├── .gitignore ├── LICENSE ├── README.md ├── SUMMARY.md ├── code ├── meta-evaluator │ ├── apply.rkt │ └── evaluator.rkt └── simula │ ├── adder.rkt │ ├── agenda.rkt │ ├── gate.rkt │ ├── prob.rkt │ ├── queue.rkt │ ├── simula.rkt │ └── wire.rkt └── page ├── learn-sicp-0 ├── blackbox.png ├── cover.jpg ├── cs.png ├── env.png ├── s1.png ├── sicp.png ├── split.png ├── tree.png ├── twoo.png └── twosimple.png ├── learn-sicp-1 ├── cover.jpg ├── lambda.png ├── let.png ├── progress_1.png ├── tree.png ├── twoo.png └── twosimple.png ├── learn-sicp-2 ├── Fib.png ├── Functor.png ├── Functor_1.png ├── church.svg ├── cover.jpg ├── endofFunctor.png ├── eval.png ├── example.png ├── floor.png ├── lib.png ├── pair.png ├── symbol.png └── tree.png ├── learn-sicp-3 ├── basic.png ├── cover.jpg ├── eval.png ├── floor.png ├── floorplus.png ├── rectangular.png ├── struct.png ├── symbol.png ├── system.png ├── tree.png └── type_tower.png ├── learn-sicp-4 ├── cover.jpg ├── environment.png ├── eval-env.png ├── eval.png ├── history.png ├── obj-his.png └── timeline.png ├── learn-sicp-5 ├── adder.png ├── and.png ├── cover.jpg ├── plus.gif ├── s-1.png ├── s-2.png ├── s-3.png ├── s-4.png └── struct.png ├── learn-sicp-6 ├── cover.jpg ├── dead.png ├── inf-pairs.png ├── lock.png ├── signal-stream.png ├── states.png └── timeline.png ├── learn-sicp-7 ├── cover.jpg ├── eval-apply.png ├── eval.png └── factorial.png ├── learn_sicp_0.md ├── learn_sicp_1.md ├── learn_sicp_2.md ├── learn_sicp_3.md ├── learn_sicp_4.md ├── learn_sicp_5.md ├── learn_sicp_6.md ├── learn_sicp_7.md └── learn_sicp_8.md /.gitignore: -------------------------------------------------------------------------------- 1 | _book/ 2 | update.sh 3 | .vscode/ 4 | 5 | # Created by https://www.gitignore.io/api/macos,racket,gitbook 6 | # Edit at https://www.gitignore.io/?templates=macos,racket,gitbook 7 | 8 | ### GitBook ### 9 | # Node rules: 10 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 11 | .grunt 12 | 13 | ## Dependency directory 14 | ## Commenting this out is preferred by some people, see 15 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # Book build output 19 | _book 20 | 21 | # eBook build output 22 | *.epub 23 | *.mobi 24 | *.pdf 25 | 26 | ### macOS ### 27 | # General 28 | .DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | 32 | # Icon must end with two \r 33 | Icon 34 | 35 | # Thumbnails 36 | ._* 37 | 38 | # Files that might appear in the root of a volume 39 | .DocumentRevisions-V100 40 | .fseventsd 41 | .Spotlight-V100 42 | .TemporaryItems 43 | .Trashes 44 | .VolumeIcon.icns 45 | .com.apple.timemachine.donotpresent 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | 54 | ### Racket ### 55 | # gitignore template for the Racket language 56 | # website: http://www.racket-lang.org/ 57 | 58 | # DrRacket autosave files 59 | *.rkt~ 60 | *.rkt.bak 61 | \#*.rkt# 62 | \#*.rkt#*# 63 | 64 | # Compiled racket bytecode 65 | compiled/ 66 | *.zo 67 | 68 | # Dependency tracking files 69 | *.dep 70 | 71 | # End of https://www.gitignore.io/api/macos,racket,gitbook -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SICP 的魔法 2 | 3 | **(整理自我自己的[博客](https://lfkdsk.github.io),担心有些同学看github page不方便,单独开一个proj)** 4 | 5 | 计算机科学的内容包罗万象,其中的经典的课程也是不胜枚举。但是在这其中SICP(Structure and Interpretation of Computer Programs)绝对是其中的经典和翘楚,在2008年以前SICP的MIT6.001课程历来是CS相关专业必修入门课程。 6 | 7 | SICP的核心内容是什么呢?众说纷云,有人说是一本有关Lisp/Scheme的书主要讲函数式编程的思想,有的说是一本有关解释器构造的入门书籍,和我们学过的龙书挂钩,但就我个人而言,SICP作为一本入门书更多的不是担负起介绍某一方面具体的知识的重任,而是从多个角度去教一个初学者从程序抽象、理解工程架构、学习DSL的构建方法......,不单纯介绍一方面的知识而是完备的形成一个闭环的去像你介绍什么是Computer Science。相比于这些当初选用`MIT Scheme`现在使用`Python`,不过是最大程度上减小编程语言本身的复杂度对学生理解的影响,个人觉得无足挂怀。 8 | 9 | SICP的各个版本的封面,都选择了魔法师作为其中的主要素材,这里也作为我这个系列的名字,让我们一起领略SICP的魔法。 10 | 11 | ## How to get? 12 | 13 | **GitBook** :[MagicSICP](https://www.gitbook.com/book/lfkdsk/magicsicp/details) 14 | 15 | > GitBook 版本可提供在线预览和 pdf、epub、mobi 格式的下载 16 | 17 | ## Chapters 18 | 19 | * [Scheme 基础和黑盒抽象](page/learn_sicp_0.md) 20 | * [过程的求值计算和高阶过程](page/learn_sicp_1.md) 21 | * [数据抽象、层次抽象](page/learn_sicp_2.md) 22 | * [符号演算和数据表示方法](page/learn_sicp_3.md) 23 | * [模块化、状态、环境](page/learn_sicp_4.md) 24 | * [实例:数字电路模拟](page/learn_sicp_5.md) 25 | * [并发、时间与流模拟](page/learn_sicp_6.md) 26 | * [元语言抽象](page/learn_sicp_7.md) 27 | 28 | ## Feedback 29 | Please send your feedback as long as there occurs any inconvenience or problem. You can contact me with: 30 | * Email: lfk_dsk@hotmail.com 31 | * Weibo: [@亦狂亦侠_亦温文](http://www.weibo.com/u/2443510260) 32 | * Blog: [刘丰恺](https://lfkdsk.github.io/) 33 | 34 | ## License 35 | 36 | [License File](LICENSE) 37 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [简介](README.md) 4 | * [Scheme 基础和黑盒抽象](page/learn_sicp_0.md) 5 | * [过程的求值计算和高阶过程](page/learn_sicp_1.md) 6 | * [数据抽象、层次抽象](page/learn_sicp_2.md) 7 | * [符号演算和数据表示方法](page/learn_sicp_3.md) 8 | * [模块化、状态、环境](page/learn_sicp_4.md) 9 | * [实例:数字电路模拟](page/learn_sicp_5.md) 10 | * [并发、时间与流模拟](page/learn_sicp_6.md) 11 | * [元语言抽象](page/learn_sicp_7.md) 12 | 13 | -------------------------------------------------------------------------------- /code/meta-evaluator/apply.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (#%require sicp) 4 | ;;;SECTION 4.1.1 5 | (define apply-in-underlying-scheme apply) 6 | (provide apply-in-underlying-scheme) -------------------------------------------------------------------------------- /code/meta-evaluator/evaluator.rkt: -------------------------------------------------------------------------------- 1 | ;;;;METACIRCULAR EVALUATOR FROM CHAPTER 4 (SECTIONS 4.1.1-4.1.4) of 2 | ;;;; STRUCTURE AND INTERPRETATION OF COMPUTER PROGRAMS 3 | 4 | ;;;;Matches code in ch4.scm 5 | 6 | ;;;;This file can be loaded into Scheme as a whole. 7 | ;;;;Then you can initialize and start the evaluator by evaluating 8 | ;;;; the two commented-out lines at the end of the file (setting up the 9 | ;;;; global environment and starting the driver loop). 10 | 11 | ;;;;**WARNING: Don't load this file twice (or you'll lose the primitives 12 | ;;;; interface, due to renamings of apply). 13 | 14 | ;;;from section 4.1.4 -- must precede def of metacircular apply 15 | #lang racket 16 | 17 | (#%require sicp) 18 | (#%require "apply.rkt") 19 | ;;;SECTION 4.1.1 20 | 21 | (define (eval exp env) 22 | (cond ((self-evaluating? exp) exp) 23 | ((variable? exp) (lookup-variable-value exp env)) 24 | ((quoted? exp) (text-of-quotation exp)) 25 | ((assignment? exp) (eval-assignment exp env)) 26 | ((definition? exp) (eval-definition exp env)) 27 | ((if? exp) (eval-if exp env)) 28 | ((lambda? exp) 29 | (make-procedure (lambda-parameters exp) 30 | (lambda-body exp) 31 | env)) 32 | ((begin? exp) 33 | (eval-sequence (begin-actions exp) env)) 34 | ((cond? exp) (eval (cond->if exp) env)) 35 | ((application? exp) 36 | (apply (eval (operator exp) env) 37 | (list-of-values (operands exp) env))) 38 | (else 39 | (error "Unknown expression type -- EVAL" exp)))) 40 | 41 | (define (apply procedure arguments) 42 | (cond ((primitive-procedure? procedure) 43 | (apply-primitive-procedure procedure arguments)) 44 | ((compound-procedure? procedure) 45 | (eval-sequence 46 | (procedure-body procedure) 47 | (extend-environment 48 | (procedure-parameters procedure) 49 | arguments 50 | (procedure-environment procedure)))) 51 | (else 52 | (error 53 | "Unknown procedure type -- APPLY" procedure)))) 54 | 55 | (define (list-of-values exps env) 56 | (if (no-operands? exps) 57 | '() 58 | (cons (eval (first-operand exps) env) 59 | (list-of-values (rest-operands exps) env)))) 60 | 61 | (define (eval-if exp env) 62 | (if (true? (eval (if-predicate exp) env)) 63 | (eval (if-consequent exp) env) 64 | (eval (if-alternative exp) env))) 65 | 66 | (define (eval-sequence exps env) 67 | (cond ((last-exp? exps) (eval (first-exp exps) env)) 68 | (else (eval (first-exp exps) env) 69 | (eval-sequence (rest-exps exps) env)))) 70 | 71 | (define (eval-assignment exp env) 72 | (set-variable-value! (assignment-variable exp) 73 | (eval (assignment-value exp) env) 74 | env) 75 | 'ok) 76 | 77 | (define (eval-definition exp env) 78 | (define-variable! (definition-variable exp) 79 | (eval (definition-value exp) env) 80 | env) 81 | 'ok) 82 | 83 | ;;;SECTION 4.1.2 84 | 85 | (define (self-evaluating? exp) 86 | (cond ((number? exp) true) 87 | ((string? exp) true) 88 | (else false))) 89 | 90 | (define (quoted? exp) 91 | (tagged-list? exp 'quote)) 92 | 93 | (define (text-of-quotation exp) (cadr exp)) 94 | 95 | (define (tagged-list? exp tag) 96 | (if (pair? exp) 97 | (eq? (car exp) tag) 98 | false)) 99 | 100 | (define (variable? exp) (symbol? exp)) 101 | 102 | (define (assignment? exp) 103 | (tagged-list? exp 'set!)) 104 | 105 | (define (assignment-variable exp) (cadr exp)) 106 | 107 | (define (assignment-value exp) (caddr exp)) 108 | 109 | 110 | (define (definition? exp) 111 | (tagged-list? exp 'define)) 112 | 113 | (define (definition-variable exp) 114 | (if (symbol? (cadr exp)) 115 | (cadr exp) 116 | (caadr exp))) 117 | 118 | (define (definition-value exp) 119 | (if (symbol? (cadr exp)) 120 | (caddr exp) 121 | (make-lambda (cdadr exp) 122 | (cddr exp)))) 123 | 124 | (define (lambda? exp) (tagged-list? exp 'lambda)) 125 | 126 | (define (lambda-parameters exp) (cadr exp)) 127 | (define (lambda-body exp) (cddr exp)) 128 | 129 | (define (make-lambda parameters body) 130 | (cons 'lambda (cons parameters body))) 131 | 132 | 133 | (define (if? exp) (tagged-list? exp 'if)) 134 | 135 | (define (if-predicate exp) (cadr exp)) 136 | 137 | (define (if-consequent exp) (caddr exp)) 138 | 139 | (define (if-alternative exp) 140 | (if (not (null? (cdddr exp))) 141 | (cadddr exp) 142 | 'false)) 143 | 144 | (define (make-if predicate consequent alternative) 145 | (list 'if predicate consequent alternative)) 146 | 147 | 148 | (define (begin? exp) (tagged-list? exp 'begin)) 149 | 150 | (define (begin-actions exp) (cdr exp)) 151 | 152 | (define (last-exp? seq) (null? (cdr seq))) 153 | (define (first-exp seq) (car seq)) 154 | (define (rest-exps seq) (cdr seq)) 155 | 156 | (define (sequence->exp seq) 157 | (cond ((null? seq) seq) 158 | ((last-exp? seq) (first-exp seq)) 159 | (else (make-begin seq)))) 160 | 161 | (define (make-begin seq) (cons 'begin seq)) 162 | 163 | 164 | (define (application? exp) (pair? exp)) 165 | (define (operator exp) (car exp)) 166 | (define (operands exp) (cdr exp)) 167 | 168 | (define (no-operands? ops) (null? ops)) 169 | (define (first-operand ops) (car ops)) 170 | (define (rest-operands ops) (cdr ops)) 171 | 172 | 173 | (define (cond? exp) (tagged-list? exp 'cond)) 174 | 175 | (define (cond-clauses exp) (cdr exp)) 176 | 177 | (define (cond-else-clause? clause) 178 | (eq? (cond-predicate clause) 'else)) 179 | 180 | (define (cond-predicate clause) (car clause)) 181 | 182 | (define (cond-actions clause) (cdr clause)) 183 | 184 | (define (cond->if exp) 185 | (expand-clauses (cond-clauses exp))) 186 | 187 | (define (expand-clauses clauses) 188 | (if (null? clauses) 189 | 'false ; no else clause 190 | (let ((first (car clauses)) 191 | (rest (cdr clauses))) 192 | (if (cond-else-clause? first) 193 | (if (null? rest) 194 | (sequence->exp (cond-actions first)) 195 | (error "ELSE clause isn't last -- COND->IF" 196 | clauses)) 197 | (make-if (cond-predicate first) 198 | (sequence->exp (cond-actions first)) 199 | (expand-clauses rest)))))) 200 | 201 | ;;;SECTION 4.1.3 202 | 203 | (define (true? x) 204 | (not (eq? x false))) 205 | 206 | (define (false? x) 207 | (eq? x false)) 208 | 209 | 210 | (define (make-procedure parameters body env) 211 | (list 'procedure parameters body env)) 212 | 213 | (define (compound-procedure? p) 214 | (tagged-list? p 'procedure)) 215 | 216 | 217 | (define (procedure-parameters p) (cadr p)) 218 | (define (procedure-body p) (caddr p)) 219 | (define (procedure-environment p) (cadddr p)) 220 | 221 | 222 | (define (enclosing-environment env) (cdr env)) 223 | 224 | (define (first-frame env) (car env)) 225 | 226 | (define the-empty-environment '()) 227 | 228 | (define (make-frame variables values) 229 | (cons variables values)) 230 | 231 | (define (frame-variables frame) (car frame)) 232 | (define (frame-values frame) (cdr frame)) 233 | 234 | (define (add-binding-to-frame! var val frame) 235 | (set-car! frame (cons var (car frame))) 236 | (set-cdr! frame (cons val (cdr frame)))) 237 | 238 | (define (extend-environment vars vals base-env) 239 | (if (= (length vars) (length vals)) 240 | (cons (make-frame vars vals) base-env) 241 | (if (< (length vars) (length vals)) 242 | (error "Too many arguments supplied" vars vals) 243 | (error "Too few arguments supplied" vars vals)))) 244 | 245 | (define (lookup-variable-value var env) 246 | (define (env-loop env) 247 | (define (scan vars vals) 248 | (cond ((null? vars) 249 | (env-loop (enclosing-environment env))) 250 | ((eq? var (car vars)) 251 | (car vals)) 252 | (else (scan (cdr vars) (cdr vals))))) 253 | (if (eq? env the-empty-environment) 254 | (error "Unbound variable" var) 255 | (let ((frame (first-frame env))) 256 | (scan (frame-variables frame) 257 | (frame-values frame))))) 258 | (env-loop env)) 259 | 260 | (define (set-variable-value! var val env) 261 | (define (env-loop env) 262 | (define (scan vars vals) 263 | (cond ((null? vars) 264 | (env-loop (enclosing-environment env))) 265 | ((eq? var (car vars)) 266 | (set-car! vals val)) 267 | (else (scan (cdr vars) (cdr vals))))) 268 | (if (eq? env the-empty-environment) 269 | (error "Unbound variable -- SET!" var) 270 | (let ((frame (first-frame env))) 271 | (scan (frame-variables frame) 272 | (frame-values frame))))) 273 | (env-loop env)) 274 | 275 | (define (define-variable! var val env) 276 | (let ((frame (first-frame env))) 277 | (define (scan vars vals) 278 | (cond ((null? vars) 279 | (add-binding-to-frame! var val frame)) 280 | ((eq? var (car vars)) 281 | (set-car! vals val)) 282 | (else (scan (cdr vars) (cdr vals))))) 283 | (scan (frame-variables frame) 284 | (frame-values frame)))) 285 | 286 | ;;;SECTION 4.1.4 287 | 288 | (define (setup-environment) 289 | (let ((initial-env 290 | (extend-environment (primitive-procedure-names) 291 | (primitive-procedure-objects) 292 | the-empty-environment))) 293 | (define-variable! 'true true initial-env) 294 | (define-variable! 'false false initial-env) 295 | initial-env)) 296 | 297 | ;[do later] (define the-global-environment (setup-environment)) 298 | 299 | (define (primitive-procedure? proc) 300 | (tagged-list? proc 'primitive)) 301 | 302 | (define (primitive-implementation proc) (cadr proc)) 303 | 304 | (define primitive-procedures 305 | (list (list 'car car) 306 | (list 'cdr cdr) 307 | (list 'cons cons) 308 | (list 'null? null?) 309 | ;; more primitives 310 | )) 311 | 312 | (define (primitive-procedure-names) 313 | (map car 314 | primitive-procedures)) 315 | 316 | (define (primitive-procedure-objects) 317 | (map (lambda (proc) (list 'primitive (cadr proc))) 318 | primitive-procedures)) 319 | 320 | ;[moved to start of file] (define apply-in-underlying-scheme apply) 321 | 322 | (define (apply-primitive-procedure proc args) 323 | (apply-in-underlying-scheme 324 | (primitive-implementation proc) args)) 325 | 326 | 327 | 328 | (define input-prompt ";;; M-Eval input:") 329 | (define output-prompt ";;; M-Eval value:") 330 | 331 | (define (driver-loop) 332 | (prompt-for-input input-prompt) 333 | (let ((input (read))) 334 | (let ((output (eval input the-global-environment))) 335 | (announce-output output-prompt) 336 | (user-print output))) 337 | (driver-loop)) 338 | 339 | (define (prompt-for-input string) 340 | (newline) (newline) (display string) (newline)) 341 | 342 | (define (announce-output string) 343 | (newline) (display string) (newline)) 344 | 345 | (define (user-print object) 346 | (if (compound-procedure? object) 347 | (display (list 'compound-procedure 348 | (procedure-parameters object) 349 | (procedure-body object) 350 | ')) 351 | (display object))) 352 | 353 | ;;;Following are commented out so as not to be evaluated when 354 | ;;; the file is loaded. 355 | (define the-global-environment (setup-environment)) 356 | (driver-loop) 357 | 358 | ; 'METACIRCULAR-EVALUATOR-LOADED -------------------------------------------------------------------------------- /code/simula/adder.rkt: -------------------------------------------------------------------------------- 1 | ; 半加器 全加器 2 | (define (half-adder a b s c) 3 | (let ((d (make-wire)) 4 | (e (make-wire))) 5 | (or-gate a b d) 6 | (and-gate a b c) 7 | (inverter-gate c e) 8 | (and-gate d e s) 9 | 'ok)) 10 | 11 | (define (full-adder a b c-in sum c-out) 12 | (let ((s (make-wire)) 13 | (c1 (make-wire)) 14 | (c2 (make-wire))) 15 | (half-adder b c-in s c1) 16 | (half-adder a s sum c2) 17 | (or-gate c1 c2 c-out) 18 | 'ok)) 19 | 20 | 21 | -------------------------------------------------------------------------------- /code/simula/agenda.rkt: -------------------------------------------------------------------------------- 1 | (load "queue.rkt") 2 | 3 | ; segment 4 | 5 | (define (make-time-segment time queue) 6 | (cons time queue)) 7 | 8 | (define (segment-time s) (car s)) 9 | 10 | (define (segment-queue s) (cdr s)) 11 | 12 | ; agenda 13 | 14 | (define (make-agenda) (list 0)) 15 | 16 | (define (current-time agenda) (car agenda)) 17 | 18 | (define (set-current-time! agenda time) 19 | (set-car! agenda time)) 20 | 21 | (define (segments agenda) (cdr agenda)) 22 | 23 | (define (set-segments! agenda segments) 24 | (set-cdr! agenda segments)) 25 | 26 | (define (first-segment agenda) (car (segments agenda))) 27 | 28 | (define (rest-segments agenda) (cdr (segments agenda))) 29 | 30 | (define (empty-agenda? agenda) 31 | (null? (segments agenda))) 32 | 33 | (define (add-to-agenda! time action agenda) 34 | (define (belongs-before? segments) 35 | (or (null? segments) 36 | (< time (segment-time (car segments))))) 37 | (define (make-new-time-segment time action) 38 | (let ((q (make-queue))) 39 | (insert-queue! q action) 40 | (make-time-segment time q))) 41 | (define (add-to-segments! segments) 42 | (if (= (segment-time (car segments)) time) 43 | (insert-queue! (segment-queue (car segments)) 44 | action) 45 | (let ((rest (cdr segments))) 46 | (if (belongs-before? rest) 47 | (set-cdr! 48 | segments 49 | (cons (make-new-time-segment time action) 50 | (cdr segments))) 51 | (add-to-segments! rest))))) 52 | (let ((segments (segments agenda))) 53 | (if (belongs-before? segments) 54 | (set-segments! 55 | agenda 56 | (cons (make-new-time-segment time action) 57 | segments)) 58 | (add-to-segments! segments)))) 59 | 60 | (define (remove-first-agenda-item! agenda) 61 | (let ((q (segment-queue (first-segment agenda)))) 62 | (delete-queue! q) 63 | (if (empty-queue? q) 64 | (set-segments! agenda (rest-segments agenda))))) 65 | 66 | (define (first-agenda-item agenda) 67 | (if (empty-agenda? agenda) 68 | (error "Agenda is empty -- FIRST-AGENDA-ITEM") 69 | (let ((first-seg (first-segment agenda))) 70 | (set-current-time! agenda (segment-time first-seg)) 71 | (front-queue (segment-queue first-seg))))) 72 | 73 | (define (after-delay delay action) 74 | (add-to-agenda! (+ delay (current-time the-agenda)) 75 | action 76 | the-agenda)) 77 | 78 | (define (propagate) 79 | (if (empty-agenda? the-agenda) 80 | 'done 81 | (let ((first-item (first-agenda-item the-agenda))) 82 | (first-item) 83 | (remove-first-agenda-item! the-agenda) 84 | (propagate)))) 85 | -------------------------------------------------------------------------------- /code/simula/gate.rkt: -------------------------------------------------------------------------------- 1 | 2 | ; 算数逻辑非 3 | (define (logical-not s) 4 | (cond ((= s 0) 1) 5 | ((= s 1) 0) 6 | (else (error " Invalid signal " s)))) 7 | 8 | ; 算数逻辑与 9 | (define (logical-and a b) 10 | (if (and (= a 1) (= b 1)) 11 | 1 12 | 0)) 13 | 14 | 15 | ; 逻辑或 16 | (define (logical-or a b) 17 | (if (or (= a 1) (= b 1)) 18 | 1 19 | 0)) 20 | 21 | ; 与门 给两个线路都绑上一个监控器 22 | ; 当某个值变化的时候 会重新计算 new-value 设置到输出端口 23 | (define (and-gate a1 a2 output) 24 | (define (and-action-procedure) 25 | (let ((new-value 26 | (logical-and (get-signal a1) (get-signal a2)))) 27 | (after-delay and-gate-delay 28 | (lambda () 29 | (set-signal! output new-value))))) 30 | (add-action! a1 and-action-procedure) 31 | (add-action! a2 and-action-procedure) 32 | 'ok) 33 | 34 | ; 或门 35 | (define (or-gate input-1 input-2 output) 36 | (define (or-action-procedure) 37 | (let ((new-value 38 | (logical-or (get-signal input-1) (get-signal input-2)))) 39 | (after-delay or-gate-delay 40 | (lambda () 41 | (set-signal! output new-value))))) 42 | (add-action! input-1 or-action-procedure) 43 | (add-action! input-2 or-action-procedure) 44 | 'ok) 45 | 46 | ; 反门 47 | (define (inverter-gate input output) 48 | (define (invert-input) 49 | (let ((new-value (logical-not (get-signal input)))) 50 | (after-delay inverter-delay 51 | (lambda () (set-signal! output new-value))))) 52 | (add-action! input invert-input) 53 | 'ok) 54 | 55 | -------------------------------------------------------------------------------- /code/simula/prob.rkt: -------------------------------------------------------------------------------- 1 | (define (probe name wire) 2 | (add-action! wire 3 | (lambda () 4 | (newline) 5 | (display name) 6 | (display " ") 7 | (display (current-time the-agenda)) 8 | (display " New-value = ") 9 | (display (get-signal wire))))) 10 | -------------------------------------------------------------------------------- /code/simula/queue.rkt: -------------------------------------------------------------------------------- 1 | ; 队列的实现 2 | (define (front-ptr queue) 3 | (car queue)) 4 | 5 | (define (rear-ptr queue) 6 | (cdr queue)) 7 | 8 | (define (set-front-ptr! queue item) 9 | (set-car! queue item)) 10 | 11 | (define (set-rear-ptr! queue item) 12 | (set-cdr! queue item)) 13 | 14 | ; queue 15 | 16 | (define (empty-queue? queue) 17 | (null? (front-ptr queue))) 18 | 19 | (define (make-queue) 20 | (cons '() '())) 21 | 22 | (define (front-queue queue) 23 | (if (empty-queue? queue) 24 | (error "FRONT called with an empty queue" queue) 25 | (car (front-ptr queue)))) 26 | 27 | (define (insert-queue! queue item) 28 | (let ((new-pair (cons item '()))) 29 | (cond ((empty-queue? queue) 30 | (set-front-ptr! queue new-pair) 31 | (set-rear-ptr! queue new-pair) 32 | queue) 33 | (else 34 | (set-cdr! (rear-ptr queue) new-pair) 35 | (set-rear-ptr! queue new-pair) 36 | queue)))) 37 | 38 | (define (delete-queue! queue) 39 | (cond ((empty-queue? queue) 40 | (error "DELETE! called with an empty queue" queue)) 41 | (else 42 | (set-front-ptr! queue (cdr (front-ptr queue))) 43 | queue))) -------------------------------------------------------------------------------- /code/simula/simula.rkt: -------------------------------------------------------------------------------- 1 | ;;; simula 2 | 3 | (load "gate.rkt") 4 | (load "adder.rkt") 5 | (load "wire.rkt") 6 | (load "agenda.rkt") 7 | (load "prob.rkt") 8 | 9 | (define the-agenda (make-agenda)) 10 | (define inverter-delay 2) 11 | (define and-gate-delay 3) 12 | (define or-gate-delay 5) 13 | 14 | 15 | (define input-1 (make-wire)) 16 | (define input-2 (make-wire)) 17 | 18 | (define sum (make-wire)) 19 | (define carry (make-wire)) 20 | 21 | (probe 'sum sum) 22 | (probe 'carry carry) 23 | 24 | -------------------------------------------------------------------------------- /code/simula/wire.rkt: -------------------------------------------------------------------------------- 1 | ; 创建一条连线 2 | (define (make-wire) 3 | (let ((signal-value 0) 4 | (action-procedures '())) 5 | ; set new value and call procedures 6 | (define (set-my-signal! new-value) 7 | (if (not (= signal-value new-value)) 8 | (begin (set! signal-value new-value) 9 | (call-each action-procedures)) 10 | 'done)) 11 | 12 | (define (accept-action-procedure! proc) 13 | (set! action-procedures 14 | (cons proc action-procedures)) 15 | (proc)) 16 | 17 | (define (dispatch m) 18 | (cond ((eq? m 'get-signal) signal-value) 19 | ((eq? m 'set-signal!) set-my-signal!) 20 | ((eq? m 'add-action!) accept-action-procedure!) 21 | (else (error "Unknown operation -- WIRE" m)))) 22 | 23 | dispatch)) 24 | 25 | ; 遍历调用 26 | (define (call-each procedures) 27 | (if (null? procedures) 28 | 'done 29 | (begin 30 | ((car procedures)) 31 | (call-each (cdr procedures))))) 32 | 33 | (define (get-signal wire) 34 | (wire 'get-signal)) 35 | 36 | (define (set-signal! wire new-value) 37 | ((wire 'set-signal!) new-value)) 38 | 39 | (define (add-action! wire action-procedure) 40 | ((wire 'add-action!) action-procedure)) 41 | -------------------------------------------------------------------------------- /page/learn-sicp-0/blackbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/blackbox.png -------------------------------------------------------------------------------- /page/learn-sicp-0/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-0/cs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/cs.png -------------------------------------------------------------------------------- /page/learn-sicp-0/env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/env.png -------------------------------------------------------------------------------- /page/learn-sicp-0/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/s1.png -------------------------------------------------------------------------------- /page/learn-sicp-0/sicp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/sicp.png -------------------------------------------------------------------------------- /page/learn-sicp-0/split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/split.png -------------------------------------------------------------------------------- /page/learn-sicp-0/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/tree.png -------------------------------------------------------------------------------- /page/learn-sicp-0/twoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/twoo.png -------------------------------------------------------------------------------- /page/learn-sicp-0/twosimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-0/twosimple.png -------------------------------------------------------------------------------- /page/learn-sicp-1/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-1/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-1/lambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-1/lambda.png -------------------------------------------------------------------------------- /page/learn-sicp-1/let.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-1/let.png -------------------------------------------------------------------------------- /page/learn-sicp-1/progress_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-1/progress_1.png -------------------------------------------------------------------------------- /page/learn-sicp-1/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-1/tree.png -------------------------------------------------------------------------------- /page/learn-sicp-1/twoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-1/twoo.png -------------------------------------------------------------------------------- /page/learn-sicp-1/twosimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-1/twosimple.png -------------------------------------------------------------------------------- /page/learn-sicp-2/Fib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/Fib.png -------------------------------------------------------------------------------- /page/learn-sicp-2/Functor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/Functor.png -------------------------------------------------------------------------------- /page/learn-sicp-2/Functor_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/Functor_1.png -------------------------------------------------------------------------------- /page/learn-sicp-2/church.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /page/learn-sicp-2/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-2/endofFunctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/endofFunctor.png -------------------------------------------------------------------------------- /page/learn-sicp-2/eval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/eval.png -------------------------------------------------------------------------------- /page/learn-sicp-2/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/example.png -------------------------------------------------------------------------------- /page/learn-sicp-2/floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/floor.png -------------------------------------------------------------------------------- /page/learn-sicp-2/lib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/lib.png -------------------------------------------------------------------------------- /page/learn-sicp-2/pair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/pair.png -------------------------------------------------------------------------------- /page/learn-sicp-2/symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/symbol.png -------------------------------------------------------------------------------- /page/learn-sicp-2/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-2/tree.png -------------------------------------------------------------------------------- /page/learn-sicp-3/basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/basic.png -------------------------------------------------------------------------------- /page/learn-sicp-3/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-3/eval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/eval.png -------------------------------------------------------------------------------- /page/learn-sicp-3/floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/floor.png -------------------------------------------------------------------------------- /page/learn-sicp-3/floorplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/floorplus.png -------------------------------------------------------------------------------- /page/learn-sicp-3/rectangular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/rectangular.png -------------------------------------------------------------------------------- /page/learn-sicp-3/struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/struct.png -------------------------------------------------------------------------------- /page/learn-sicp-3/symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/symbol.png -------------------------------------------------------------------------------- /page/learn-sicp-3/system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/system.png -------------------------------------------------------------------------------- /page/learn-sicp-3/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/tree.png -------------------------------------------------------------------------------- /page/learn-sicp-3/type_tower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-3/type_tower.png -------------------------------------------------------------------------------- /page/learn-sicp-4/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-4/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-4/environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-4/environment.png -------------------------------------------------------------------------------- /page/learn-sicp-4/eval-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-4/eval-env.png -------------------------------------------------------------------------------- /page/learn-sicp-4/eval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-4/eval.png -------------------------------------------------------------------------------- /page/learn-sicp-4/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-4/history.png -------------------------------------------------------------------------------- /page/learn-sicp-4/obj-his.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-4/obj-his.png -------------------------------------------------------------------------------- /page/learn-sicp-4/timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-4/timeline.png -------------------------------------------------------------------------------- /page/learn-sicp-5/adder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/adder.png -------------------------------------------------------------------------------- /page/learn-sicp-5/and.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/and.png -------------------------------------------------------------------------------- /page/learn-sicp-5/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-5/plus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/plus.gif -------------------------------------------------------------------------------- /page/learn-sicp-5/s-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/s-1.png -------------------------------------------------------------------------------- /page/learn-sicp-5/s-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/s-2.png -------------------------------------------------------------------------------- /page/learn-sicp-5/s-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/s-3.png -------------------------------------------------------------------------------- /page/learn-sicp-5/s-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/s-4.png -------------------------------------------------------------------------------- /page/learn-sicp-5/struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-5/struct.png -------------------------------------------------------------------------------- /page/learn-sicp-6/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-6/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-6/dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-6/dead.png -------------------------------------------------------------------------------- /page/learn-sicp-6/inf-pairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-6/inf-pairs.png -------------------------------------------------------------------------------- /page/learn-sicp-6/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-6/lock.png -------------------------------------------------------------------------------- /page/learn-sicp-6/signal-stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-6/signal-stream.png -------------------------------------------------------------------------------- /page/learn-sicp-6/states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-6/states.png -------------------------------------------------------------------------------- /page/learn-sicp-6/timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-6/timeline.png -------------------------------------------------------------------------------- /page/learn-sicp-7/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-7/cover.jpg -------------------------------------------------------------------------------- /page/learn-sicp-7/eval-apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-7/eval-apply.png -------------------------------------------------------------------------------- /page/learn-sicp-7/eval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-7/eval.png -------------------------------------------------------------------------------- /page/learn-sicp-7/factorial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lfkdsk/SICP-Magical-Book/4f4d4822ed27834ded7c782d9874c73a6d3378d0/page/learn-sicp-7/factorial.png -------------------------------------------------------------------------------- /page/learn_sicp_0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0x00:SICP 的魔法 - Scheme 基础和黑盒抽象 3 | date: 2017-02-21 22:22:14 4 | tags: SICP 5 | --- 6 | 7 | ![COVER](learn-sicp-0/cover.jpg) 8 | 9 | > 作者 :[刘丰恺](https:;github.com/lfkdsk) 10 | > 11 | > 作者博客:[若梦浮生](https:;lfkdsk.github.io/) 12 | > 13 | > 转载需征得作者本人同意 14 | 15 | 计算机科学的内容包罗万象,其中的经典的课程也是不胜枚举。但是在这其中SICP(Structure and Interpretation of Computer Programs)绝对是其中的经典和翘楚,在2008年以前SICP的MIT6.001课程历来是CS相关专业必修入门课程。 16 | 17 | SICP的核心内容是什么呢?众说纷云,有人说是一本有关Lisp/Scheme的书主要讲函数式编程的思想,有的说是一本有关解释器构造的入门书籍,和我们学过的龙书挂钩,但就我个人而言,SICP作为一本入门书更多的不是担负起介绍某一方面具体的知识的重任,而是从多个角度去教一个初学者从程序抽象、理解工程架构、学习DSL的构建方法......,不单纯介绍一方面的知识而是完备的形成一个闭环的去像你介绍什么是Computer Science。相比于这些当初选用`MIT Scheme`现在使用`Python`,不过是最大程度上减小编程语言本身的复杂度对学生理解的影响,个人觉得无足挂怀。 18 | 19 | SICP的各个版本的封面,都选择了魔法师作为其中的主要素材,这里也作为我这个系列的名字,让我们一起领略SICP的魔法。 20 | 21 | ## 学习之前 22 | 23 | 在正式开始之前,我们先简单的了解几个问题。 24 | 25 | ### 我们如何看待Computer Science? 26 | 27 | ![cs](learn-sicp-0/cs.png) 28 | 29 | 很惊人对吧,第一次看到这个`NO COMPUTER NO SCIENCE`的时候我也是被这种说法吓到了。但是对于这个说法的讲解倒也是能自圆其说。 30 | 31 | 首先是`NO SCIENCE`,作者在课上说CS不像是一门科学更像是一门艺术或者是工程。工程好理解,但是艺术听起来就很玄之又玄的感觉,但是这里笔者想谈谈自己的感受,对于笔者个人而言,编程像是一种写作,就想写作当前这篇文章的感觉是一样的,代码/文字从手中流淌出来,形成程序/文章,两者可以说是近乎相同的。 32 | 33 | 再说这个`NO COMPUTER`,作者认为这门学科也不是完全和计算机有关,就像几何学不一定合圆规和量角器有关系一样,文以载道,计算机只是帮助我们实现这些功能的工具而已,这也就是为什么变成会被称作和魔法相同,编写代码/编写咒语,即使我们生活在一个没有计算机的魔法世界,我们仍然能学习这门课程(当然不会再被称之为CS了)。 34 | 35 | ### 定义和过程的理解 36 | 37 | ![s1](learn-sicp-0/s1.png) 38 | 39 | 我们首先来看这个公式,这是一个对于平方根的`定义`,和我们在数学书上学到的一样。给我们一个y的值我们可以很方便的确定是不是x的平方根,但是这个公式并不能告诉我们平方根到底是怎么求的,也就是说上文只是在告诉我们平方根到底是什么。 40 | 41 | 但是如果要涉及怎么求平方根,我们就需要借助牛顿迭代法了,通过猜测一个数字,再根据求出商,两者相加求平均值作为下一次的平方根猜测量,这样逐步逼近到达一个最接近的数值就是x的平方根。 42 | 43 | > 如下求 2 的平方根 44 | 45 | 46 | | 猜测量 | 商 | 平均值 | 47 | | ------ | ------------------- | ------------------------------- | 48 | | 1 | 2/1 = 2 | (2 + 1) / 2 = 1.5 | 49 | | 1.5 | 2 / 1.5 = 1.3333 | (1.5 + 1.3333) / 2 = 1.4167 | 50 | | 1.4167 | 2 / 1.4167 = 1.4118 | (1.4167 + 1.4118 ) / 2 = 1.4142 | 51 | | 1.4142 | ... | | 52 | 53 | 这个使用牛顿迭代法的步骤就可以称作为是`过程`,因为它解决了我们如何求解平方根这个问题,告诉了我们怎么做。 54 | 55 | ## Scheme 基础 56 | 57 | 之前我们说过,选择 Scheme 看重的是它的函数性特性,但是更重要的是它的简洁易用能减少学习语言的负担。 Scheme 是一门解释器语言,其实也有编译器的实现,不过这都不重要。解释器语言是一个接受输入返回数据的大循环(Read-Evaluate-Print Loop),就像是聊天的交互一样,每接受一段输入,就返回这段代码的执行结果,这就是书封面图的由来: 58 | 59 | ![sicp](learn-sicp-0/sicp.png) 60 | 61 | ​ **(一个Eval和Apply互生的球)** 62 | 63 | ### 构造语言的三个基本要素 64 | 65 | * 基本表达式形式:构造各种程序的基础 66 | * 组合机制:简单的表达式构造更复杂的表达式 67 | * 抽象机制:为复杂的结构命名,通过简单方式使用 68 | 69 | 这三者的意义都好理解,但是在这里还要针对 Scheme 的语法特性去分别理解,暂且按下不表。 70 | 71 | ### 语言处理的两种要素 72 | 73 | 除此之外程序设计包含两类处理要素: 74 | 75 | * 过程 76 | * 数据 77 | 78 | 这两者我们也都熟悉,在我们学过的大部分静态语言中如:`C`(这个越来越难举例子了,Cpp/Java都有自己的函数式实现)中,过程就是代码程序,数据呢就是我们定义的变量,两者楚河汉界、泾渭分明。 79 | 80 | 但是在 Scheme 中就像那个太极图一样,过程和数据的界限越来越模糊,**数据可作为被执行的代码,代码可作为被处理的数据**。 81 | 82 | ### 简单的语法知识 83 | 84 | 这里介绍一些简单的语法知识用来学习更多的相关知识。 85 | 86 | #### 表达式 87 | 88 | 在 Scheme 中 表达式中最基本的表达式就是一个数字: 89 | 90 | ``` lisp 91 | > 486 ; 输入 92 | 486 93 | ``` 94 | 95 | 简单的表达式,使用前缀的形式,括号里第一个元素表示操作(运算),后面是参数(运算对象)运算符和参数之间、不同参数之间用空格分隔: 96 | 97 | ``` lisp 98 | > (+ 34 5) 99 | 39 100 | > (* 3 4) 101 | 12 102 | ``` 103 | 104 | #### 组合子 105 | 106 | 表达式可以进行嵌套和组合: 107 | 108 | ``` lisp 109 | > (+ 1 (* 3 4)) 110 | 13 111 | ``` 112 | 113 | **注意使用合理的代码书写规范 ** 114 | 115 | #### 命名和环境 116 | 117 | 对变量和过程进行命名: 118 | 119 | ``` lisp 120 | > (define size 15) ; 定义变量 121 | size 122 | > (define (getSize x) ; 定义过程 123 | (* x 3)) 124 | getSize 125 | > (getSize 12) 126 | 36 127 | ``` 128 | 129 | 小标题里面提到了环境(environment)这个意义可能需要介绍一下,其实这个环境和作用域和可见性都是相关的概念,你可以理解为程序有一个全局的`Map`存储了很多的键值对,Key是名字,Value是数据还是过程都可以。除此以外,程序的每一层`block`(代码块)都有一个自己的`Map`存储当前环境下的键值对,这个存储的`Map`就可以简单理解为当前的环境。 130 | 131 | > Tips: 与C语言对比: 132 | > 133 | > * C里面没有明显的环境定义,但是从变量的可见性/覆盖/上能看出环境。 134 | > * C中的表达式在当前环境中求值,语句可能修改当前环境中有效定义的变量(Scheme的区别之后会详解)。 135 | > * C中有明确的类型系统,Scheme 连数据和过程都模糊了,这个自然也不会有。 136 | 137 | ### 小结 138 | 139 | 使用上面三个小节的简单的 Lisp 知识,我们几乎就可以开始编写代码了,虽然我们还没有介绍分枝结构,循环结构,上面的三个小节已经覆盖了构成一个语言的三要素: 140 | 141 | - 基本表达式形式:构造各种程序的基础 *** / + -** 142 | - 组合机制:简单的表达式构造更复杂的表达式 **( )** 143 | - 抽象机制:为复杂的结构命名,通过简单方式使用 **define** 144 | 145 | ## 黑箱 146 | 147 | > Tips: 其余有用的简单语法知识 148 | > 149 | > * 条件表达式 150 | > 151 | > ``` lisp 152 | > ; 类似switch语法结构的cond子句 153 | > (define (abs x) 154 | > (cond ((> x 0) x) 155 | > ((= x 0) x) 156 | > ((< x 0) -x))) 157 | > ; if子句 158 | > (define (abs x) 159 | > (if (< x 0) 160 | > (-x) 161 | > x)) 162 | > ``` 163 | > * 复合谓词 164 | > 165 | > ``` lisp 166 | > ; and 167 | > (and expr1 expr2...) 168 | > ; or 169 | > (or expr1 expr2...) 170 | > ; not 171 | > (not expr1) 172 | > ``` 173 | 174 | ### 再看平方根 175 | 176 | 之前我们提到了用牛顿迭代法求平方根的过程,整个过程就像是这样的一个黑箱一样,我们不需要知道`过程`实现的细节,就可以直接使用: 177 | 178 | ![black-box](learn-sicp-0/blackbox.png) 179 | 180 | 再从分解的角度上看结合我们在第一节里面的讨论,再加上我们已经学过了一些 Scheme 的内容,我们能得出这样的计算平方根的代码: 181 | 182 | ```lisp 183 | (define (sqrt-iter guess x) 184 | (if (good-enough? guess x) 185 | guess 186 | (sqrt-iter (improve guess x) 187 | x))) 188 | ``` 189 | 190 | ​ 求值的过程,被分解成了几个子函数来计算,`good-enough?`是一个判断对精度的判断,`improve`是求平均值,总而言之,略去这些函数的定义,这个算法其实相当的轻便和易懂。 191 | 192 | ​ 算法的分解图如下: 193 | 194 | ![block-split](learn-sicp-0/split.png) 195 | 196 | 这也是黑箱的另一种角度,从结构可以看到,不但过程是用户的黑箱,过程和过程之间也是存在着护卫黑箱的关系的,比如`sqare`是`good-enough`的黑箱,`good-enough`又是`sqrt-iter`的黑箱。 197 | 198 | 所以这里我们能得到这样的结论: 199 | 200 | * **应该把被用的过程看作黑箱,关注功能,不关注其实现** 201 | * **过程抽象的本质是一种功能分解,各个子过程之间也互为黑箱** 202 | 203 | ### 过程抽象的实现手段 204 | 205 | * 局部名: 206 | 207 | ``` lisp 208 | (define (square x) (* x x)) 209 | (define (square y) (* y y)) 210 | ``` 211 | 212 | 这东西其实很好理解,上面的两个定义本身不因形参名字的改变而改变,x是这个过程的`约束比变量`,可以理解为作用域逃不出当层环境,只是一个`占位符`。 213 | 214 | 这其中如果`自由变量`(类比可见性强的变量)和`约束变量`名同就会被约束变量替换,该环境下拿到的变量就是当前环境x,而非全局的x了。 215 | 216 | * 内部定义和块结构: 217 | 218 | Scheme 支持在过程内定义过程,这其实也很简单,也是一种隐蔽细节的手段,通过内部定义减少全局环境的名字,来避免干扰,可以类比定义类的私有函数。 219 | 220 | 事实上,`环境`可以使用特别简单的模型去考虑: 221 | 222 | ![env](learn-sicp-0/env.png) 223 | 224 | 层层相连,每层都是键值对,当解释器遇到一个变量的时候,从最内层的环境去找,找到了就返回,没找到就继续向上层找,这也就是为什么会出现变量`捕获`的情况出现。 225 | 226 | ### 小结 227 | 228 | 隐蔽细节的黑箱操作能够为组织代码带来很多优势,从中能得出很多启示: 229 | 230 | * 控制名字的作用范围,防止不同`环境`之间的名字冲突 231 | * 信息尽可能做到局部化 232 | * 块结构对控制程序的复杂性很有价值 233 | 234 | -------------------------------------------------------------------------------- /page/learn_sicp_1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0x01:SICP 的魔法 - 过程的求值计算和高阶过程 3 | date: 2017-02-27 12:01:15 4 | tags: SICP 5 | --- 6 | 7 | ![cover](learn-sicp-1/cover.jpg) 8 | 9 | ## 过程的求值计算 10 | 11 | 这里面我们先来介绍一种最为简单、通用的求值模型,`代换模型`并不能概括全部的求值方式,但是我们先从这个开始。 12 | 13 | ### 代换模型 14 | 15 | 通用的表达式计算模式,描述起来其实非常简单,先求出各子表达式的值,找到要调用的过程的定义,用求出的实际参数代换过程体里的形式参数,再对过程体进行求值,本质上一种使用等价性的表达式的拆分机制,比如下例,是对一个平方和函数进行计算的详细步骤: 16 | 17 | ```lisp 18 | (define (sum-of-squares x y) 19 | (+ (square x) (square y))) 20 | ; sum-of-squares 求两个数的平方和 21 | (define (f x) (sum-of-squares (+ x 1) (* x 2))) 22 | (f 5) 23 | (sum-of-squares (+ 5 1) (* 5 2)) ; 注意这两行,首先算出来形参 24 | (+ (square 6) (square 10)) 25 | (+ (* 6 6) (* 10 10)) 26 | (+ 36 100) 27 | 136 28 | ``` 29 | 30 | > Tips: 上面的代换方式又被称作`应用序`的计算方式,这也是 Scheme 解释器的计算方式,除此之外,还有一种被称作`正则序`的计算方式,正则性和上面不一样的地方在于,它不会先计算出调用过程的形参,反倒是一定要把整个表达式最小化到所有的东西都能直接计算的程序(摊开了的感觉): 31 | > 32 | > ```lisp 33 | > (f 5) 34 | > (sum-of-squares (+ 5 1) (* 5 2)) 35 | > (+ (square (+ 5 1)) (square (* 5 2)) ); 注意这两行这里没往下计算形参 36 | > (+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2))) 37 | > (+ (* 6 6) (* 10 10)) ; 反倒是都摊开了才开始进行规约 38 | > (+ 36 100) 39 | > 136 40 | > ``` 41 | > 42 | > PS: 有个很简单的Demo能证明所用的解释器到底用了什么计算顺序: 43 | > 44 | > ```lisp 45 | > (define (p) (p)) 46 | > (define (test x y) 47 | > (if (= x 0) 48 | > 0 49 | > y)) 50 | > ``` 51 | > 52 | > 大家可以想想`正则序`和`应用序`分别会有什么结果。 53 | 54 | ### 迭代与递归 55 | 56 | 我们首先从两个简单例子开始: 57 | 58 | ![twosimple](learn-sicp-1/twosimple.png) 59 | 60 | 图中有两个简单的例子,计算上是等价的都是对a、b进行相加的等价型(这里使用`plus`是为了区别基本符号),但是我们活学活用一下刚才学过的代换模型的知识就会发现,这两个过程的计算方式是不同的: 61 | 62 | ![twoo](learn-sicp-1/twoo.png) 63 | 64 | 这里我们抛开别的我们很明显就能发现,这两种计算方式计算过程的图形长得就不一样,`方案1`里面层层代换之后表达式越来越长,最后开始规约。但是`方案2`就没有这个过程,每次都是单一的一步层层调用,这样的话实现这两种算法的时间复杂度就天差地别,`方案1`是一个`O(n)`的计算方式,层层递归的展开。而`方案2`的调用是线性次数的迭代,时间复杂度就是`O(1)`的。 65 | 66 | 这里我们就称第一种求值方式是`线性递归`,第二种方式是`线性迭代`。 67 | 68 | > Tips: 这一节的递归和迭代和我们在其余编程语言里学习的有所区别。这里面说的是计算方式,而非语法实现,如果从语法角度来讲这两个都调用了自己都是`语法递归`。 69 | > 计算的`线性递归`的低效还体现在中间结果存储需要空间,整体形成一个较大的运行栈。而`线性迭代`的中间结果只是中间结果的数值和计数器数值,当计算机掉电后也容易恢复,可以在计算中任何一步中断和重启。 70 | 71 | ### 树形结构 72 | 73 | 说起树形结构我们先举一个例子,斐波那契数列: 74 | $$ 75 | 76 | Fib(n) = 77 | \begin{cases} 78 | 0, & \text{n = 0 } \\\\ 79 | n, & \text{n = 1} \\\\ 80 | Fib(n-1) + Fib(n-2), & \text{others} 81 | \end{cases} 82 | $$ 83 | 84 | 这是斐波那契数列的规则定义,我们也经常用这个方式去使用递归编写代码,简单的写成 Scheme 的样子呢应该就是下面这个样子: 85 | 86 | ```lisp 87 | (define (Fib x) 88 | (if (< N 2) 89 | N 90 | (+ (Fib (- N 1)) (Fib (- N 2)))) 91 | ``` 92 | 93 | 按照我们之前的步骤去分析整个过程的求值过程,能画出这样的一个树形的图: 94 | 95 | ![tree](learn-sicp-1/tree.png) 96 | 97 | 这个过程是非常低效的,从图中有非常多重复的计算过程,比如`Fib 2` ,`Fib1`都计算了两次,并且斐波那契的时间复杂度是指数级的,这个重复会随着计算数据的增大而继续的增大。 98 | 99 | 如何优化? 100 | 101 | 我们看到之前的`线性迭代`提到了只保存了**中间结果的数值和计数器数值**,我们可以借助这个思路去优化我们的斐波那契数列。 102 | 103 | 我们使用`a`,`b`两个变量保存中间结果,斐波那契数列是`Fib(0)=0`,`Fib(1)=1`,其余等于前两个数之和,所以首先给a/b赋值为1, 之后: 104 | $$ 105 | a\Leftarrow a + b\\\\ 106 | b\Leftarrow a 107 | $$ 108 | 这样反复的迭代下去,最后`a`,`b`就会保存最终结果`Fib(n+1)`,`Fib(n)`。 109 | 110 | 写成代码的形式是这样的: 111 | 112 | ```lisp 113 | (define (fib n) 114 | (define (fib-iter a b count) 115 | (if (= count 0)) 116 | b 117 | (fib-iter (+ a b) a (- count 1))) 118 | (fib-iter 1 0 n) 119 | ) 120 | ``` 121 | 122 | 这样一个树形的递归就会变成一个常数时间的线性的线性迭代。 123 | 124 | > „ Tips: 尾递归形式和尾递归优化 125 | > 126 | > - ‰一个递归定义的过程称为是尾递归的,如果其中对本过程的递归调用都是过程**执行的最后一个表达式** 127 | > - 虽然是递归定义过程,计算所需的存储却不随递归深度增加。尾递归技术就是重复使用原过程在执行栈里的存储,不另行分配 128 | 129 | ### 实例求值 130 | 131 | PS: 增长的阶 => Big O 分析法 132 | $$ 133 | O(1) < O(log n) < O(n) < O(n log n) < O(n^2) < O(n^3) < ... < O(2^n) \\\\ 134 | 常量\qquad 对数\qquad 线性\qquad 平方\qquad\qquad 立方\qquad \qquad\qquad\qquad指数\qquad 135 | $$ 136 | Example: 137 | 138 | * 幂运算:求`b`的`n`次幂 139 | 140 | $$ 141 | b{^n} = b * b ^{n-1} \\\\ 142 | b^0 = 1 143 | $$ 144 | 145 | ``` lisp 146 | ; 线性递归方式 O(n)的时间复杂度 O(n)的空间复杂度 147 | (define (expt b n) 148 | (if (= n 0) 149 | 1 150 | (expt b n - 1))) 151 | ; 线性迭代方式 O(n) 的时间复杂度 O(1)的空间复杂度 152 | (define (expt b n) 153 | (expt-iter b n 1)) 154 | 155 | (define (expt-iter b n result) 156 | (if (= n 0) 157 | result 158 | (expt-iter b (- n 1) (* result b)))) 159 | ``` 160 | 161 | 二分法 O(log(n))的时间复杂度: 162 | $$ 163 | b^n = (b^{n/2}) \text { n为偶数}\\\\ 164 | b^n = b*b^{n-1} \text { n为奇数} 165 | $$ 166 | 167 | ``` lisp 168 | (define (fast-expt b n) 169 | (cond ((= n 0) 1) 170 | ((even? n) (square (fast-expt b (/ n 2)))) 171 | (else (* b (fast-expt b (- n 1)))))) 172 | ``` 173 | 174 | * 最大公约数: 175 | 176 | 欧几里得原理的 Scheme 描述,算法的增长阶: 177 | 178 | ``` lisp 179 | (define (GCD a b) 180 | ((if (= b 0) 181 | a 182 | (GCD b (reminder a b))))) 183 | ``` 184 | 185 | * 素性检测: 186 | 187 | 通常的素性检测,是检测是否有小于 $ \sqrt{n} $ 的素数,这是一个 $ ch $ 的算法。 188 | 189 | 费马检查提供了$ O(log(n)) $ 的计算方式: 190 | $$ 191 | a^n \equiv a \mod n 192 | $$ 193 | 取任意`a < n`,代入公式求`a^n mod n`等不等于`n`。如果等于,就有可能是素数,但是如果不是肯定不是素数,然后不断的取随机数进行`概率检测`增加素性测试的概率。 194 | 195 | ``` lisp 196 | (define (fermat-test n) 197 | (define (try-it a) 198 | (= (expmod a n n) a) 199 | (try-it (+ 1 (random (- n 1)))))) 200 | 201 | (define (fast-prime? n times) 202 | (cond (fermat-test n) (fast-prime n (- times 1))) 203 | (else false))) 204 | ``` 205 | 206 | 使用了概率测试,提高了求素数的概率,能骗过`费马检测`的数字特别少可以忽略不计。 207 | 208 | ## 高阶函数抽象 209 | 210 | 我们从之前的学习中能看出,过程也是一类抽象,不依赖特定的数字,却能描述数之间的相互关系: 211 | 212 | ``` lisp 213 | (define (cube x) (* x x x)) 214 | ``` 215 | 216 | 但是如果一个过程只能传入数,那过程抽象的描述能力显然要大打折扣,我们之前也已经说过了,数据和过程的界限将变得越来越模糊,这样子我们的过程不但能当成参数传入还能当成参数返回,还拥有$ \lambda $ 表达式这样拥有匿名和闭包特性的高阶应用。 217 | 218 | 我们在这样的一下情况下需要用到高阶过程: 219 | 220 | * ‰ 一些计算具有相似的模式,只是其中涉及的几个操作不同 221 | * 要利用公共模式需要把这几个操作参数化 222 | * 具有参数化操作的过程,就是高阶过程(一种) 223 | 224 | ### 过程作为参数 225 | 226 | 我们来先看这几个计算过程,都是很简单的过程: 227 | 228 | ![progress](learn-sicp-1/progress_1.png) 229 | 230 | 我们能很容易的在这三个累加式中找到共性,他们的区别是每个item的构成不同,但是组合方式都是对`a,b`进行操作,而且从整体来看都是每个item的累加,这样我们就能从中抽象出一个通式: 231 | 232 | ``` lisp 233 | (define ( a b) 234 | ((if (> a b) 235 | 0 236 | (+ ( a) 237 | ( ( a) b)))) 238 | ``` 239 | 240 | 这其中``代表了函数名,``代表了对`a`进行的操作,`` 代表了一种改变参数的步进,计算了下一步的a的值。 241 | 242 | 这时候我们发现我们对a的操作``和``已经被抽象了出来,如果这两个过程能被当成参数传入,那么这样的通式就能很好的概括上面三个计算公式的过程了,恰好 Scheme 就支持以过程作为参数,我们做累加的的这段代码就可以这样写: 243 | 244 | ``` lisp 245 | ; next 的步进 246 | (define (next x) (+ x 1)) 247 | ; term 248 | (define (term x) x) 249 | ; 通式 250 | (define (sum term a next b) 251 | (if (> a b) 252 | 0 253 | (+ (term a) (sum (next a) b)))) 254 | ; 累加的包装 255 | (define (sum-integers a b) 256 | (sum term a next b)) 257 | ``` 258 | 259 | 通过这样的一个特性支持的高阶函数,我们就非常轻易的解决了之前那些的冗余的代码,将一个迭代累加的过程抽象成了一个通式,只需要传入过程就好了。 260 | 261 | > C 语言等传统语言是无法传递过程的,但是可以传递函数指针,完成类似的效果, 262 | > 263 | > C里面让结构体包含过程就是通过结构体携带函数指针来完成的。 264 | > 265 | > ``` c 266 | > typedef double (*add) (int); // 函数名和类型 267 | > ``` 268 | 269 | ### $ \lambda $ 的构造 270 | 271 | 上一小节里面我们做出来一个累加的通式,为了好理解我是把好几个函数分开定义的,但是我们之前知道,子国策和国内的定义可以写在过程内,就像这样: 272 | 273 | ``` lisp 274 | (define (sum-integers a b) 275 | (define (next x) (+ x 1)) 276 | (define (term x) x) 277 | (sum term a next b) 278 | ) 279 | ``` 280 | 281 | 写成这样,子过程的安全性和可达性都得到了保证,但是我们不禁要问,`next`和`term`这两个变量真有用么?我们只是把他们创建出来就扔进了`sum`过程,之后他们就再没什么用了,这时候我们就应该引入$ \lambda $表达式,使用过函数式语言的同学对这个应该不会陌生: 282 | 283 | ``` lisp 284 | ; 比如刚才的 next 就可以写成这种形式 285 | (lambda (x) (+ x 1)) 286 | ``` 287 | 288 | $ \lambda $ 表达式的语法是这样的: 289 | 290 | ![lambda](learn-sicp-1/lambda.png) 291 | 292 | $ \lambda $ 表达式提供了这样的一个匿名的过程,这时候我们再写这个累加函数的时候就可以写成这个样子的了: 293 | 294 | ``` lisp 295 | (define (sum-intergers a b) 296 | (sum 297 | (lambda (x) (+ x 1) 298 | a 299 | (lambda (x) x 300 | b)) 301 | ``` 302 | 303 | 是不是方便了很多。 304 | 305 | > Tips: lambda 表达式的求值 306 | > 307 | > 之前我们学过 Scheme 应用序的求值顺序,会先对函数的参数进行求值 308 | > 309 | > 但是 lambda 是一个特殊的过程,参数不参与求值 310 | 311 | 还有: 312 | 313 | > Tips: lambda 创建的是对象,只不过也同样是过程而已 314 | > 315 | > 和`define`创建的过程还是有所区别的,但是因为是过程,所以可以当作组合子 316 | > 317 | > 的运算符来用。 318 | 319 | ### 使用`let`创建局部变量 320 | 321 | 另外 $ \lambda $ 表达式还提供了使用`let`创建局部变量的方法: 322 | 323 | 我们有的时候程序需要一些中间变量,虽然也能用`define`去定义,但是`define`最好还是用来定义内部过程比较好。 324 | 325 | 还有就是通过定义一个辅助的子过程去代理这个中间变量,但是面对更复杂的情况,会使得代码结构不清晰。 326 | 327 | SICP上的一个例子: 328 | $$ 329 | f(x,y) = x(1 + xy)^2 + y (1-y) + (1 + xy)(1-y) 330 | $$ 331 | 我们希望能将这个式子简化,里面的重复步骤实在是太多了,如果全都直接写出来肯定很坑的会被计算好几次,我们可以把这个简写成这样: 332 | $$ 333 | a = 1 + xy\\\\ 334 | b = 1-y\\\\ 335 | f(x,y) = xa^2 + yb + ab 336 | $$ 337 | 这时候我们就需要抽象出`a`,`b`这两个变量,我们这时候就可以引入`let`表达式: 338 | 339 | ![let](learn-sicp-1/let.png) 340 | 341 | `let` 表达式可以提供多个类似键值对的局部变量,可见性仅在这个let表达式内,可以应用在下面的`body`中。 342 | 343 | ``` lisp 344 | (define (f x y) 345 | (let ((a (+ 1 (* x y))) 346 | (b (- 1 y))) 347 | (+ (* x (square a)) 348 | (* y b) 349 | (* a b)))) 350 | ``` 351 | 352 | `let`表达式具有这样的一些特性: 353 | 354 | * let 尽可能在接近使用的地方创建局部变量约束。 355 | * let也同样会有变量覆盖的情况出现。 356 | 357 | 358 | ### 过程作为返回值 359 | 360 | 回到我们最开始的求平方根的问题,我们已经知道了求一个数的平方根等价于求: 361 | $$ 362 | f(y) = x/y 363 | $$ 364 | 的一个不动点,我们可以很轻松的写出这样的代码: 365 | 366 | ``` lisp 367 | (define (sqrt x) 368 | (fixed-point (lambda (y) (/ x y)) 1.0 )) 369 | ; fixed-point 那一套之前已经太熟悉了,就是调用传入的过程,不断看数据的趋近是否小于某个参量 370 | ``` 371 | 372 | 但是这样子的式子是明显没办法正常收敛的,我们不过就是在反复的尝试两个数字而已。这时我们在上一节里面的那个办法的作用就体现出来了,还记得么?我们使用了: 373 | $$ 374 | x_2 = (x_1 + f(x1) )/2 375 | $$ 376 | 作为下一次使用的变量传入,然后再通过不断的迭代直到最后的结果收敛到一个固定的值求得不动点。 377 | 378 | 这种通过平均值来解决函数呈现波动趋势的方法被叫做`平均阻尼法` ,通过这种平均阻尼的逼近方法我们可以把上面的那个代码改变一下,这样不动点就可以正常的逼近了: 379 | 380 | ``` lisp 381 | (define (sqrt x) 382 | (fixed-point) (lambda (y) (average y (/ x y)) 383 | 1.0)) 384 | ``` 385 | 386 | 我们现在已经把里面的传入的过程换成了我们所使用的平均阻尼的方法了,但是这样的过程明显是不能满足我们的,我们假定SICP这章主要谈的是有关过程抽象的话题(实际上也是这样),其实平方根的话题只是个不错的引子。 387 | 388 | 其实这里面出现的问题就是里面的方法又不具备通用性了,我们之前也说了`平均阻尼`是一类解决逼近问题的通用方法,单就求不动点的函数来说,也可以针对各种的过程使用平均阻尼的方法的。这时候我们就希望语言可以给予我们生成方法的方法,这样我们只需要传入需要进行平均阻尼加工的方法就可以,我们就可以直接使用平均阻尼来求不动点了,当然 Scheme 自然提供了这种方法,我们可以把刚才描述的的表达式写成这种样子: 389 | 390 | ``` lisp 391 | (define (average-damp f) 392 | (lambda (x) (average x (f x)))) 393 | ``` 394 | 395 | 这里我们只需要传入一个方法,就可以生成对应的平均阻尼方法了,比如我们想求`cos`函数的不动点,我们就可以写`(average-damp cos)`就可以了,这个过程的参数是过程,返回值也是一个过程,但是经过我们之前对 Scheme 的理解,对于这种方式一点都不感觉惊奇。事实上在计算中生成新过程是前面没遇到过的新问题,实际上这是lambda 表达式最重要的作用。 396 | 397 | > Tips: 作为一等公民的过程: 398 | > 399 | > * 可以用变量命名(在常规语言里,可存入变量,取出使用) 400 | > * ‰可以作为参数传给过程 401 | > * 可以由过程作为结果返回 402 | > * 可以放入各种数据结构 403 | > * 可以在运行中动态地构造 404 | 405 | ## 总结 406 | 407 | 这一章我们接触了很多相对基础的知识,从一个求函数平方根(不动点)的问题为核心,我们接触了 Scheme 的基础语法,讨论语言的数值计算问题,甚至还谈到了高阶过程(lambda表达式、Lisp 语言中过程的第一地位),这些都是和传统的 OO 语言有很大的区别的,甚至说思想都可以说是这种函数式语言的一个核心观点了。我的学习路径一直都很诡异,很多的理论接触之前就不小心做过一些实践,其实有的时候这样对知识的把握是不牢靠的。SICP这本书看起来很简单,但是我觉得还是应该仔细的阅读,毕竟能把很多思想核心的问题说的如此的浅显易懂还是很看原书作者功底的。 -------------------------------------------------------------------------------- /page/learn_sicp_2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0x02:SICP 的魔法 - 数据抽象、层次抽象 3 | date: 2017-03-11 18:17:35 4 | tags: SICP 5 | --- 6 | 7 | ![cover](learn-sicp-2/cover.jpg) 8 | 9 | 在第一章里面,我们已经见识到了`过程`抽象的魔法,一个过程描述了一系列数据的计算过程,但本身又是一种元素可以出现在程序的任何部分,所以说过程是一种抽象,我们在使用的时候不需要知道任何和具体实现有关的东西,只需要调用我们已经定义好的过程就行了。 10 | 11 | 与此相类,`数据`本身也可以作为一种抽象,我们在之前接触的数据都是一些简单的数据,所以可能没什么感受。但是数据也可以包含不止一种的信息,使用的时候隐藏具体的实现细节,具体使用的时候又能作为元素出现在程序任意的位置,因此数据也是一种抽象。 12 | 13 | > 有过 OO 语言经验的同学 可以借助类的概念理解一下以上的概念 14 | > 15 | > 但是`类`的概念是无法完全概括的哦~ 16 | 17 | ## 数据抽象 18 | 19 | 我们从最简单的数据抽象开始,首先说最小的数据抽象集合 —— 序对。 20 | 21 | ### 序对 22 | 23 | 要实现数据抽象首先要有能把数据打成一个包的方法,我们叫它`构造函数`,还应该有能把数据从捆中取出来的方法,我们叫他`选择函数`。 24 | 25 | 在 Scheme 中提供了能实现这些方法的 API: 26 | 27 | | Function Name | Usage | 28 | | ------------- | --------------------------- | 29 | | ( cons p1 p2) | 能把两个参数打包成一个对象 | 30 | | ( car x ) | 能从 `cons` 打包出的对象 取出其中的第一个数据 | 31 | | ( cdr x ) | 能从 `cons` 打包出的对象 取出其中的第二个数据 | 32 | 33 | 对于能非常方便构建 `class` 或是 `struct` 这样的数据结构的其他语言的使用者来看,这个序对的作用实在是微乎其微,但是大的抽象模式都是从最小的抽象方式开始的,我们这里使用序对也只是为了演示 Scheme 的抽象能力。 34 | 35 | ### 如何定义有理数? 36 | 37 | 这看起来似乎不是个问题,因为语言都会原生支持各种类型的浮点数,能轻松的用来表示有理数,但是请先忘了有关这方面的知识,单纯考虑当我们的系统只能支持整形数据的时候我们应该怎么表示有理数。 38 | 39 | 从上一小节的序对的知识出发,我们很容易找到答案,我们可以把有理数的小数点前后的部分,分别用一个整形数据来表示,再把他们用 `cons` 打包,当进行计算的时候再拆开计算就可以了。 40 | 41 | 所以我们对于有理数的定义甚至都可以用上面的函数来表示 : 42 | 43 | | Function Name | Usage | 44 | | ----------------- | ----------- | 45 | | ( make-rat x y ) | 生成有理数 x.y | 46 | | ( number bundle ) | 获取 x.y 中的 x | 47 | | ( denom bundle ) | 获取 x.y 中的 y | 48 | 49 | 写实现也很简单 : 50 | 51 | ``` lisp 52 | ; 直接打包 53 | (define (make-rat x y) 54 | (cons x y)) 55 | ; 拿第一个数 56 | (define (number bundle) 57 | (car bundle)) 58 | ; 拿第二个数 59 | (define (number bundle) 60 | (cdr bundle)) 61 | ``` 62 | 63 | 我们这样就可以用上面的 API 去实现有理数的四则运算什么的 : 64 | 65 | ``` lisp 66 | (define (add-rat x y) 67 | (make-rat (+ (* (numer x) (denom y)) 68 | (* (numer y) (denom x))) 69 | (* (denom x) (denom y)))) 70 | (define (sub-rat x y) 71 | (make-rat (- (* (numer x) (denom y)) 72 | (* (numer y) (denom x))) 73 | (* (denom x) (denom y)))) 74 | ; ... 还有很多 75 | ``` 76 | 77 | 这样我们会发现 `add-rat` 、`sub-rat` 这样的函数作为过程抽象,仅接受我们定义的 `有理数` 类型 ( 即 `cons` 包裹的类型 ) 的数据就可以了,我们甚至完全不需要知道有理数到底是什么,只需要把有理数提交给过程,就可以拿到返回的有理数类型的返回结果了。 78 | 79 | 数据抽象和过程抽象相辅相承,在他们的帮助之下,我们为系统增加了类型,这无疑是一种新的进步,我们可以自己制作类型了。 80 | 81 | ### 实现抽象屏障 82 | 83 | 刚才的有理数程序可以体现为这样的一张层次图:  84 | 85 | ![floor](learn-sicp-2/floor.png) 86 | 87 | 可以看出来,对于不同层次的程序来讲,有理数的意义都是不同的,对于使用的程序来讲,有理数就是一个普通元素,到了需要表示有理数的层次,有理数被分成了分子分母来使用,到了序对表述的层次,有理数是通过某种系统实现,将两个整形数据绑定在一起的。 88 | 89 | 实现这样的抽象屏障有什么好处?包括但不限于: 90 | 91 | * 不同的部分关联极少,可以独立修改和增添方法。 92 | * 修改实现方便,减少错误的发生。 93 | * 修改方便的话,我们对设计就也有帮助,一些决策可以推迟。 94 | 95 | > Tips: 修改实现如何体现方便呢? 96 | > 97 | > 举个例子,如果我们换了实现 `序对` 的 API,那我们也只需要修改最下面一层就可以全部修改。相反,如果我们所有的方法都依赖于 最下层实现,比如如果我们的 `add-rat` 方法里面还是用了 `car` 、`cdr` 这样的方法,那我们如果想彻底改掉这套 API 需要修改下方全部的三层实现。 98 | 99 | ### 数据的过程实现 100 | 101 | 我们之前使用的有理数程序,仅靠了三个基本过程去定义,而没有看到具体的实现过程,那么“数据到底是什么呢”? 102 | 103 | 数据的抽象实现有很多种办法,这里我们挑一种不依赖低层实现,尽在 Scheme 中就能实现的方法,通过过程去实现数据抽象: 104 | 105 | ``` lisp 106 | ; cons 定义 107 | (define (cons x y) 108 | (define (dispatch m) 109 | (cond ((= m 0) x) 110 | ((= m 1) y) 111 | (else (error "Argument error "))))) 112 | ; car / cdr 定义 113 | (define (car z) (z 0)) 114 | (define (cdr z) (z 1)) 115 | ``` 116 | 117 | 当然 Scheme 中的数据实现肯定不是这么做的,为了效率很定是通过底层实现的。但是从这个例子里我们可以回顾在本书开头的一句话,过程和数据之间没有绝对界限,这里就看到了过程可以直接表示数据的能力。 118 | 119 | > Tips : Church encoding 丘奇计数 120 | > 121 | > 看起来很鬼畜,但是可以通过完全通过 $ \lambda $ 演算去实现全部的整数运算系统。 122 | > 123 | > 比如说像下图一样,我们用 lambda 的层数去表达数字,还可以通过这个来实现运算。 124 | > 125 | > ![church](learn-sicp-2/church.svg) 126 | > 127 | > Test 2.6 就和这个有关。 128 | 129 | ## 层次抽象 130 | 131 | 我们有了序对的具体实现,但也并不是说我们就只能把两个数据捆绑在一起,我们的 Bundle 也可以作为序对的一个元素出现,这样子我们就可以通过层次结构去实现线性结构,我们通常可以把它们描绘成一个盒图的形式: 132 | 133 | ![pair](learn-sicp-2/pair.png) 134 | 135 | 我们可以说这种特性是一种闭包性质。 136 | 137 | > Tips 闭包的含义: 138 | > 139 | > * 书里面的意思应该是说组合数据对象的结果仍能继续组合,这是符合数学含义的意义( list 中的元素在运算下封闭) 140 | > * `Closure` 的另一个含义,是指带有自由变量的过程,`Closure Function` 和普通函数一样拥有作用并且在传递的过程中仍能携带这些自由变元。 141 | 142 | ### 序列的形成 143 | 144 | 从上图可以看出,我们的 `Pair` 可以通过结成链的形式形成一个线性的序列,实际上这看起来和我们所知道的线性表的实现如出一辙,通过把 `cons` 过程的层层嵌套我们得到了一个线性表: 145 | 146 | ``` lisp 147 | ; 通过这样的方式结成链 148 | (cons 1 149 | (cons 2 150 | (cons 3 151 | (cons nil)))) 152 | ; 当然我们也可以直接使用 list API 153 | (list 1 2 3) 154 | ``` 155 | 156 | 在结成了一个序列之后,我们的过程 `car` 和 `cdr` 都有了新的意义,`car` 可以取出 list 中的第一个元素, 而 `cdr` 返回的是第一个元素以外的剩余元素所形成的列表。 157 | 158 | 有了数据表,就可以实现很多常见的表操作了,比如: 159 | 160 | ``` lisp 161 | (define (length items) 162 | (if (null? items) 163 | 0 164 | (+ 1 (length (cdr items))))) 165 | ; 求表的长度 166 | (define (append list1 list2) 167 | (if (null? list1) 168 | list2 169 | (cons (car list1) (append (cdr list1) list2)))) 170 | ; 合并两个数据表 171 | ``` 172 | 173 | > Tips:数据表在现在流行语言中是常见的数据结构,无论是 CPP STL 里面的 list 还是 Java 中的 List 系列的数据结构都是从 Lisp 的表中吸取了很多的特性。 174 | 175 | ### 高阶操作和层次结构 176 | 177 | 一些现代的、支持函数式的语言都提供了类似 `map` 、`reduce` 、`filter` 这些高阶函数的支持,都是实现了一种映射的操作。用其中的 `map` 举例子,它的实质是提供了一个对所有元素操作的映射,所得到的序列顺序不变。 178 | 179 | 比如我们可以写一个对每个元素等比例放大的例子: 180 | 181 | ``` lisp 182 | (define (scale-list items factor) 183 | (if (null? items) 184 | nil 185 | (cons (* (car items) factor) 186 | (scale-list (cdr items) factor)))) 187 | 188 | (scale-list (list 1 2 3 4 5) 10) 189 | (10 20 30 40 50) 190 | ``` 191 | 192 | 如果我们将这个过程在提高一层抽象我们就可以得到了 `map` 的定义了: 193 | 194 | ``` lisp 195 | (define (map proc items) 196 | (if (null? items) 197 | nil 198 | (cons (proc (car items)) 199 | (map proc (cdr items))))) 200 | 201 | ; 我们可以很方便的用 map 去实现 scale-list 202 | (map lambda(x) (* x 10) (list 1 2 3 4 5)) 203 | (10 20 30 40 50) 204 | ``` 205 | 206 | `map` 操作是一个重要的里程碑,这表明了我们可以脱离对序列的具体操作,而专注于对元素的操作上面。映射的层次从元素的等级提升到了表层。 207 | 208 | 我们可以把本身是元素的节点也替换成 `list` 去推广这个映射方式,那线性的表就变成了立体的树: 209 | 210 | ![tree](learn-sicp-2/tree.png) 211 | 212 | 树形结构的便利就很有迹可循了,可以自然地用递归方式处理。仍然使用之前对序列的例子(对序列进行等比例放缩),相应的我们可以写出相应的 Scheme 代码: 213 | 214 | ``` lisp 215 | (define (scale-tree tree factor) 216 | (cond ((null? tree) nil) 217 | ((not (pair? tree)) (* tree factor)) 218 | (else (cons (scale-tree (car tree) factor) 219 | (scale-tree (cdr tree) factor))))) 220 | 221 | (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) 222 | (10 (20 (30 40) 50) (60 70)) 223 | ``` 224 | 225 | 当然本身这个过程也可以使用 `map` 进行抽象: 226 | 227 | ``` lisp 228 | (define (scale-tree tree factor) 229 | (map (lambda (sub-tree) 230 | (if (pair? sub-tree) 231 | (scale-tree sub-tree factor) 232 | (* sub-tree factor))) 233 | tree)) 234 | ``` 235 | 236 | 树本身也可以看做一个序列,我们可以使用 `map` 映射到子树的每一个节点,然后再分别对每一个节点进行处理,有子树的向下继续递归,叶子就翻倍。 237 | 238 | ### 序列操作 239 | 240 | 我们先举两个例子: 241 | 242 | ![example](learn-sicp-2/example.png) 243 | 244 | 这两个问题看起来有很大的不同,但是其实两个过程本身都有一些通性: 245 | 246 | | 表现/类型 | 枚举 | 累积 | 过滤 | 选出的数操作 | 247 | | ----- | --------- | ------ | ---------- | -------- | 248 | | 求平方和 | 枚举所有的树叶 | 累计求平方和 | 过滤奇数 | 平方和 | 249 | | Fib | 枚举所有的 Fib | Fib 求和 | 过滤偶数 k <=n | 求 Fib(k) | 250 | 251 | 可以画成这样的流程图: 252 | 253 | ![lib](learn-sicp-2/lib.png) 254 | 255 | 上图表现出了一个信息流的传递过程,在程序中表现出这个信息的传递过程,就能让数据操作更为清晰。 256 | 257 | > Tips: 链式调用和流式 API 258 | > 259 | > 事实上链式调用和流式 API 在现代的编程中起到很大的作用。无论是 jQuery 还是 Reactive 系的各类 程序库,都使用了这种方式去设计 API。 260 | 261 | > Tips: 信息流结构 262 | > 263 | > * 需要注意表示和处理从一个步骤向下一步骤流动的信息 264 | > * 表适合表示和传递这些信息,通过表操作实现各步处理 265 | 266 | #### filter 267 | 268 | Filter 过滤器的实现思路很简单,递归发现不满足条件的跳过,满足条件的继续,最后重整成一个表。 269 | 270 | ``` lisp 271 | (define (filter predicate sequence) 272 | (cond ((null? sequence) nil) ; 不满足的丢掉 273 | ((predicate (car sequence)) 274 | (cons (car sequence) ; 满足条件的保留绑定在一起 275 | (filter predicate (cdr sequence)))) 276 | (else (filter predicate (cdr sequence))))) 277 | ``` 278 | 279 | #### accumulate 280 | 281 | 累加器之前已经有过介绍了,通过传入操作符,递归使用操作符进行计算。 282 | 283 | ``` lisp 284 | (define (accumulate op initial sequence) 285 | (if (null? sequence) 286 | initial 287 | (op (car sequence) 288 | (accumulate op initial (cdr sequence))))) 289 | ``` 290 | 291 | #### enumerate 292 | 293 | 树和序列的枚举之前都已经出现过了,可以直接使用。 294 | 295 | #### 重整序列 296 | 297 | 重整过的序列操作,就可以通过以上提供的 API 进行整理,我们求一棵树奇数树叶的平方和的过程就可以定义出来了: 298 | 299 | ``` lisp 300 | (define (sum-odd-squares tree) 301 | (accumulate + 302 | 0 303 | (map square 304 | (filter odd? (enumerate-tree tree))))) 305 | ``` 306 | 307 | 我们求 `Fib` 的程序中也可以通过改变上面这些序列的顺序去表示: 308 | 309 | ``` lisp 310 | (define (even-fibs n) 311 | (accumulate cons 312 | nil 313 | (filter even? 314 | (map fib (enumerate-interval 0 n))))) 315 | ``` 316 | 317 | > Tips : flatmap 的过程 318 | > 319 | > 用过使用函数式编程库的人都会很熟悉 flatmap 这个过程,flatmap 被描述为一个映射积累的过程。 320 | > 321 | > ``` lisp 322 | > (define (flatmap proc seq) 323 | > (accumulate append nil (map proc seq))) 324 | > ``` 325 | > 326 | > 首先对 seq 中的每一个元素映射,然后再把他们累积起来。**Monad 的类型都是实现了 `flatmap` 过程**。下面一节就来解释一下这句话到底是什么意思。 327 | 328 | ## 理解Monad 329 | 330 | > A monad is just a monoid in the category of endofunctors, what's the problem? 331 | 332 | 这是一个老梗了,翻译过来应该是说:“一个函子不过就是自函子范畴上的一个幺半群而已,有什么难理解的?”。 333 | 334 | 那我们理解也应该从这个比较数学的角度去理解 Monad 相关的概念。 335 | 336 | ### Semigroup 337 | 338 | 一个 `Group` 是非空的集合 ,针对群上的 二元运算`&`,(group,&) 群支持下面几项性质: 339 | * 封闭性(Closure):对于任意$ a,b∈G ​$,$有a\text{&}b∈G ​$ 340 | * 结合律(Associativity):对于任意$ a,b,c∈G $,有$(a\text{&}b)\text{&}c = a\text{&}(b\text{&}c)$ 341 | * 幺元 (Identity):存在幺元e,使得对于任意$ a∈G,e\text{&}a=a\text{&}e=a $ 342 | * 逆元:对于任意$a∈G$,存在逆元$a^{-1}$,使得$a^{-1}\text{&}a=a\text{&}a{^-1}=e$ 343 | 344 | 345 | 除此以外,这几项性质还可以互相组合: 346 | 347 | * 如果仅满足封闭性和结合律,则称G是一个半群`Semigroup`。 348 | * 如果仅满足封闭性、结合律并且有幺元,则称G是一个含幺半群`Monoid`。 349 | 350 | 这个思想如果通过代码来描述出来其实会比较清晰: 351 | 352 | ``` java 353 | public interface SemiGroup { 354 | T append(T a, T b); 355 | } 356 | 357 | public abstract class Monoid implement SemiGroup { 358 | protect T identityValue; 359 | } 360 | 361 | // String 的 demo 362 | public class StringMonoid extend Monid { 363 | protect String identityValue = ""; 364 | public String append(String a, String b) { 365 | return a + b; 366 | } 367 | } 368 | ``` 369 | 370 | 那我们说`StringMonoid` 就是一个幺半群的实例。你可以从`String` 的理解去理解幺半群这个概念,存在幺元(“” 空字符),存在封闭性(合成的字符串对 String 封闭),存在结合律(`append` 的嵌套使用)。 371 | 372 | ### Functor 373 | 374 | > Tips 范畴 375 | > 376 | > 范畴论中的范畴概念由三部分组成: 377 | > 378 | > * 对象 379 | > * 态射 380 | > * 态射组合 381 | > 382 | > 态射就是上图中的 `Int => String` 绑定两个对象,从 `Int` 到 `String` 。 383 | > 384 | > 态射组合是由多个态射组合成的对象 385 | 386 | 387 | 388 | > Tips 范畴公理 389 | > 390 | > * 态射的组合操作要满足结合律 391 | > * 范畴中任何对象存在单位态射(A => A / Int => Int) 392 | > * 态射在复合操作下是闭合的 393 | 394 | 395 | 396 | 397 | `Functor` 通常被翻译成`函子`,函子表示着范畴之间的转换关系,我们可以用这样的图来表示: 398 | 399 | ![functor](learn-sicp-2/Functor.png) 400 | 401 | 这里我们假设里面范畴1 里面有两个类型(a 和 b), $ a => b $ , 范畴2 里面有两种类型(F a 和 F b),$ Fa => Fb $ ,这两个范畴之间还存在着映射,$ a => Fa $ 和 $ b => Fb $ ,我们就称两个范畴之间的映射是**函子(Functor)** 。 402 | 403 | 我们可以用 `Integer` 和 `String` 之间的关系去更方便的去理解: 404 | 405 | ![Functor_1](learn-sicp-2/Functor_1.png) 406 | 407 | 这其中 `自函子` 是一类比较特殊的函子,它是一种将范畴映射到自身的函子: 408 | 409 | ![endof](learn-sicp-2/endofFunctor.png) 410 | 411 | 那 `Functor(函子)` 到底做了一件什么事情呢,我们以`functor((Int => String) => (List[Int] => List[String])`函子大概做了这样的三件事: 412 | 413 | * 生成出所有元素的态射结果 414 | * 对生成的结果执行 F 变化,生成集合的集合 415 | * 在自然变换下,将集合的集合合并成一个大的集合 416 | 417 | 418 | ### Monad 419 | 420 | > A monad is just a monoid in the category of endofunctors 421 | 422 | 这说明 Monad 作为一个幺半群,它的元素都是自函子。这个范畴的态射既包含元态射,也包含组合态射。 423 | 424 | 假设我们现在有一个范畴 `Friends` ,list本身是一个 Monad: 425 | 426 | ``` java 427 | list = [ "A","B","C","D","E","E"] 428 | ``` 429 | 430 | 这个范畴里的`态射`是`findFriend` 函数: 431 | 432 | ``` java 433 | AFriendList = A.findFriend() 434 | ``` 435 | 436 | 我们做的第一步就是生成了所有元素的态射对象的结果,针对每一个人找寻他们的朋友,获得到了每个人的朋友的集合。 437 | 438 | 然后全体使用 Functor 映射到另一个范畴上生成集合的集合: 439 | 440 | ``` java 441 | A => List[A] // 元素全集 442 | AFriendList => List[AFriendList] // 朋友的 list 全集 443 | ``` 444 | 445 | 这时候这个 list 的构成是这样的: 446 | 447 | ``` java 448 | list = [ 449 | [a - friends], 450 | [b - friends], 451 | ... 452 | ] 453 | ``` 454 | 455 | 我们再把这个数组拍成平面的,就获得了最后所求的全部的朋友列表: 456 | 457 | ``` java 458 | list = [ a-friends, b-friends ,...] 459 | ``` 460 | 461 | 我们会发现这个操作恰恰和我们之前提到的 `flatmap` 操作是相同的,我们把某个操作映射到一个集合里面,然后又把所有的结果形成一个集合返回。所以说**Monad 的类型都实现了 `flatmap` 过程**。 462 | 463 | ## 小结 464 | 465 | 这一节里面我们学到了和数据抽象和层次抽象的知识,数据的抽象体现在了形成序列和抽象屏障两方面,层次抽象体现在了在高阶过程的定义和通过序列的操作,最后还真对 `flatmap` 的操作简单的介绍了一些和 Monad (单子)的 知识,下一节我们会看到更为有趣的符号运算和数据的多重表示。 -------------------------------------------------------------------------------- /page/learn_sicp_3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0x03:SICP 的魔法 - 符号演算和数据表示方法 3 | date: 2017-03-20 16:24:51 4 | tags: SICP 5 | --- 6 | 7 | ![cover](learn-sicp-3/cover.jpg) 8 | 9 | ## 符号数据 10 | 11 | 有过CPP,Java等 OO 语言编程经验的人,肯定对基本类型和各种封箱的引用类型的区别有很大的感触。符号数据是什么,在这里的开始,我们可以先粗浅得把他理解成对象的引用,当然 Scheme 中的符号数据和这个还是有很大的区别的,理解这个符号我们可以先举一个这个例子: 12 | 13 | ![symbol](learn-sicp-3/symbol.png) 14 | 15 | 两句话就能看明白 `Symbol` 和 `Value` 的区别,第一句话让我们说我们最喜欢的颜色,第二句是说“你最喜欢的颜色”。自然语言中能区分**词语本身**和**词语的含义**的不同,Scheme 中也有类似的机制。 16 | 17 | ### 表示符号数据 18 | 19 | 在 Scheme 的解释器中,我们之前已经了解到了,解释器就是一个不断接收数据的 `eval()` 循环: 20 | 21 | ![eval](learn-sicp-3/eval.png) 22 | 23 | 每当我们输入表达式,就会接到表达式的返回值。同时我们还知道了,块级作用域的的实现本身是通过层层的 Map 来实现的,那我们只需要一个过程从对应的作用域表里面取出对应名字的引用就可以实现符号数据了。在 Sheme 中我们通常会使用 `'` 符号代表这个值是一个符号类型而非基本类型: 24 | 25 | ``` lisp 26 | > (define A 10) 27 | > A 28 | > ; Value: 10 使用的是基础类型 29 | > 'A 30 | > ; Value: a 使用的符号类型 31 | ``` 32 | 33 | 但是直观上来看这个引号似乎破坏了 Scheme 中的语法,如果为了 `'` 在解释器里单独实现一套机制难免得不偿失,而且是一种给解释器开洞的行为。但实际上在 Scheme 中这个问题被很轻松的解决了,`'` 本身实际上是 `(quote x)` 的一种语法糖。 34 | 35 | > Tips : **eq?** 和 **equal?** 和 **symbol?** 36 | > 37 | > * eq? 判断是不是一个引用 38 | > * equal? 判断字面是否相等 39 | > * symbol? 判断元素是不是符号 40 | 41 | ### 符号求导 42 | 43 | 书中讨论了简化版的求导函数,导函数完全由乘积和求和合成: 44 | 45 | $$ 46 | \frac{dc}{dx} = 0 \\ 47 | \frac{dx}{dx} = 1\\ 48 | \frac{d(u+v)}{dx} = \frac{du}{dx} + \frac{dv}{dx} \\ 49 | \frac{d(uv)}{dx} = u(\frac{dv}{dx}) + v(\frac{du}{dx}) 50 | $$ 51 | 52 | 从以上的几条表达式来看,后两个表达式的定义形成了递归,所以在实际上求导就会将整个的表达式进行逐步的拆分,一只拆分到最简形式,然后就可以靠前面的两条分别进行规约。 53 | 54 | 我们先写出为了实现符号求导,我们都需要哪些辅助的函数: 55 | 56 | 57 | | function | usage | 58 | | ---------------------- | ---------- | 59 | | (variable? e) | 判断 e 是不是变量 | 60 | | (same-variable? v1 v2) | 判断是否是相同变量 | 61 | | (sum? e) | e 是一个求和式? | 62 | | (addend e) | 被加数 | 63 | | (augend e) | 加数 | 64 | | (make-sum a1 a2) | 构建和式 | 65 | | (product? e) | e 是一个乘式? | 66 | | (multiplier e) | 被乘数 | 67 | | (multiplicand e) | 乘数 | 68 | | (make-product m1 m2) | 构建乘式 | 69 | 70 | 之前我们有了符号表示的方法,符号表达式也可以很方便的构建出来: 71 | 72 | ``` lisp 73 | (define (variable? x) (symbol? x)) 74 | 75 | (define (same-variable? v1 v2) 76 | (and (variable? v1) (variable? v2) (eq? v1 v2))) 77 | 78 | (define (make-sum a1 a2) (list '+ a1 a2)) 79 | 80 | (define (make-product m1 m2) (list '* m1 m2)) 81 | ; 通过 list 绑定在一起 82 | 83 | (define (sum? x) 84 | (and (pair? x) (eq? (car x) '+))) 85 | 86 | (define (addend s) (cadr s)) 87 | (define (augend s) (caddr s)) 88 | ``` 89 | 90 | 有了上面的这些辅助的 API 我们可以来尝试写一下符号求导的过程: 91 | 92 | ``` lisp 93 | (define (deriv exp var) 94 | (cond ((number? exp) 0) ; 两种基础条件 95 | ((variable? exp) 96 | (if (same-variable? exp var) 1 0)) 97 | ((sum? exp) ; 构建和式 分别求导 98 | (make-sum (deriv (addend exp) var) 99 | (deriv (augend exp) var))) 100 | ((product? exp) ; 构建乘式 分别求导 101 | (make-sum (make-product (multiplier exp) 102 | (deriv (multiplicand exp) var)) 103 | (make-product (deriv (multiplier exp) var) 104 | (multiplicand exp)))) 105 | (else (error "unknown expression type -- DERIV" exp)))) 106 | ``` 107 | 108 | > Tips : 最后写出来的程序和原表达式实在是别无二致 109 | > 110 | > 但是这里面的求导也是有问题的,比如说求出来的导数其实是没有进行化简和约分的。这时候我们就需要修改我们的程序了。但是这时候就体现出我们实现抽象屏蔽的优势了,我们可以通过只改 `make-sum` 和 `make-product` 函数来控制约分的过程。 111 | > 112 | > ``` lisp 113 | > (define (make-sum a1 a2) 114 | > (cond ((=number? a1 0) a2) 115 | > ((=number? a2 0) a1) ; 处理 0 乘积 116 | > ......) 117 | > 118 | > (define (make-product a1 a2) 119 | > (cond ((or (=number? m1 0) (=number? m2 0)) 0) ; 处理参数 0 和 1 120 | > ((=number? m1 1) m2) 121 | > ((=number? m2 1) m1) 122 | > ......)) 123 | > ``` 124 | 125 | ### 集合 126 | 127 | 抽象数据当然也能形成集合,一个集合就是一些不同对象的汇集。形成集合需要满足 `union-set` , `intersection-set` 128 | 129 | `element-of-set?` , `adjoin-set` 等操作。当然从另一个角度上来说,集合的实现也可以多种多样的,只要实现以上的操作的数据结构,我们都可以将它看作一个集合。实际实现方式的选择有很大灵活性,可以基于实际需要考虑,可以用任何合理、有效、易编程的方式表示集合。 130 | 131 | 132 | #### 未排序的表 133 | 134 | ``` lisp 135 | (define (element-of-set? x set) ; 判断元素是否在集合中 136 | (cond ((null? set) false) 137 | ((equal? x (car set)) true) 138 | (else (element-of-set? x (cdr set))))) 139 | 140 | (define (adjoin-set x set) ; 元素添加进集合 141 | (if (element-of-set? x set) 142 | set 143 | (cons x set))) 144 | 145 | (define (intersection-set set1 set2) ; 求交集 146 | (cond ((or (null? set1) (null? set2)) '()) 147 | ((element-of-set? (car set1) set2) 148 | (cons (car set1) (intersection-set (cdr set1) set2))) 149 | (else (intersection-set (cdr set1) set2)))) 150 | ``` 151 | 152 | 直接选用表结构来实现集合非常的简单,但是效率可能就有一些缺憾,判断成员时需要扫描整个表,是$ O(n) $ 操作;加入元素需判断存在性,$O(n)$求交集是 $ O(n*m) $ 操作。 153 | 154 | #### 排序的表 155 | 156 | 我们可以使用已经排序的表来缓解效率上的问题,我们先假定所有的元素都可以进行排序。我们可以采取升序的方式存储数据,这样子在查找的时候边界情况就可以帮我们减少很多的遍历负担,平均的时间复杂度下降到 $ O(n/2) $ 的水平: 157 | 158 | ``` lisp 159 | (define (element-of-set? x set) 160 | (cond ((null? set) false) 161 | ((= x (car set)) true) 162 | ((< x (car set)) false) 163 | (else (element-of-set? x (cdr set))))) 164 | ``` 165 | 166 | 而且求交集的操作也有了很大的便利,我们可以通过比较最小元素的方式进行判断,如果相等就加入并集,不相等就丢弃较小的数继续比较(有点归并排序的意思): 167 | 168 | ``` lisp 169 | (define (intersection-set set1 set2) 170 | (if (or (null? set1) (null? set2)) 171 | '() 172 | (let ((x1 (car set1)) (x2 (car set2))) 173 | (cond ((= x1 x2) 174 | (cons x1 (intersection-set (cdr set1) (cdr set2)))) 175 | ((< x1 x2) (intersection-set (cdr set1) set2)) 176 | ((< x2 x1) (intersection-set set1 (cdr set2))))))) 177 | ``` 178 | 179 | 这样子操作的代价就从 $ O(nm) $ 下降到了 $ O(m+n) $ 的程度了。 180 | 181 | > 书中还讨论了符号表示的二叉树和霍夫曼树的形式,这里就不赘述了。 182 | 183 | ## 数据的多重表示 184 | 185 | 在之前的几节之中我们见过有理数的表示方法,我们通过建立抽象屏蔽的方式,构建了我们的有理数系统,能够数据分层实现,并且提供了未来拓展功能的可能性,可以使程序中的大部分描述与数据对象的具体表示无关。 186 | 187 | > Tips 实现数据抽象的方法: 188 | > 189 | > * 用构造函数和选择函数构筑起抽象屏障 190 | > * 在屏障之外只通过这组基本操作使用数据抽象 191 | > * 大任务可以被分解为一系列的小任务 192 | 193 | 我们在系统中没有办法让一个事物或者数据有标准通用的表示方式。因为就像我们实现的那个抽象屏蔽方式,各层中对数据的形式要是不同的,而且数据对象可以有多种合理的表示形式,系统可能同时都需要使用,那我们就要实现对数据的多重表示。当然我们可以去实现各种数据之间的相互的转换关系,使用的时候判断然后进行数据类型的转换。但是是用这种方式,数据在系统中会进行特别多次的转换,效率和使用便利都得不到保证。 194 | 195 | 就像在应用里我们可能会和其他人进行合作,我们要让我们的 API 别人使用的时候也能得到便利而不需要对数据进行修改(这对我们的程序部分也有影响),而使用了数据抽象就能同时存在同一类数据的多种不同表示,并很好地支持不同表示的数据之间的相互操作。 196 | 197 | ![pic](learn-sicp-3/basic.png) 198 | 199 | ### 复数的表示 200 | 201 | 这里我们使用 `复数` 作为我们的研究对象,复数有多种的表示方法,可以通过 `实部` 和 `虚部` 描述一个复数(直角坐标系方式),还可以通过 `模` 和 `幅角` 进行描述(极坐标方式),两种方式各有千秋,使用第一种方式的程序对加减法更为友好,后者则对乘除法比较有优势。 202 | 203 | 根据我们在有理数那节的经验,我们可是仿照的画出复数系统的结构图: 204 | 205 | ![struct](learn-sicp-3/struct.png) 206 | 207 | 我们可以写出这样的定义代码去定义复数的程序: 208 | 209 | ``` lisp 210 | (define (add-complex z1 z2) 211 | (make-from-real-imag (+ (real-part z1) (real-part z2)) 212 | (+ (imag-part z1) (imag-part z2)))) 213 | (define (sub-complex z1 z2) 214 | (make-from-real-imag (- (real-part z1) (real-part z2)) 215 | (- (imag-part z1) (imag-part z2)))) 216 | (define (mul-complex z1 z2) 217 | (make-from-mag-ang (* (magnitude z1) (magnitude z2)) 218 | (+ (angle z1) (angle z2)))) 219 | (define (div-complex z1 z2) 220 | (make-from-mag-ang (/ (magnitude z1) (magnitude z2)) 221 | (- (angle z1) (angle z2)))) 222 | ``` 223 | 224 | 上面的复数调用程序很简单的,但是为了实现这个复数包的程序我们需要实现的复数包程序就要做一下考虑了,因为在这四个 API 之下,我们可是有两种下层实现的。我们有了可选择的方向,我们无论是倚重哪一种表现形式,都只会对自己这方的 API 有利(毕竟存储成自己所需要的形式,自己这方的 API 就只需要做提取操作就可以了),对另一方的 API 就需要使用转换了。 225 | 226 | 我们可以从直角坐标的角度去定义复数包的方法: 227 | 228 | ``` lisp 229 | (define (real-part z) (car z)) 230 | 231 | (define (imag-part z) (cdr z)) 232 | 233 | ; 做相应的转换 实部和虚部 => 模和幅角 234 | (define (magnitude z) 235 | (sqrt (+ (square (real-part z)) (square (imag-part z))))) 236 | (define (angle z) (atan (imag-part z) (real-part z))) 237 | 238 | (define (make-from-real-imag x y) (cons x y)) 239 | 240 | (define (make-from-mag-ang r a) (cons (* r (cos a)) (* r (sin a)))) 241 | ``` 242 | 243 | 也可以从极坐标的角度去定义复数包的方法: 244 | 245 | ``` lisp 246 | ; 做相应的转换 模和幅角 => 实部和虚部 247 | (define (real-part z) (* (magnitude z) (cos (angle z)))) 248 | (define (imag-part z) (* (magnitude z) (sin (angle z)))) 249 | 250 | (define (magnitude z) (car z)) 251 | 252 | (define (angle z) (cdr z)) 253 | 254 | (define (make-from-real-imag x y) 255 | (cons (sqrt (+ (square x) (square y))) 256 | (atan y x))) 257 | (define (make-from-mag-ang r a) (cons r a)) 258 | ``` 259 | 260 | 到这里我们拥有了有关复数包底层的两种实现,通过直角坐标的定义版本,还有极坐标定义的版本,我们可以自由的替换我们底层的方法。 261 | 262 | > Tips : 最小允诺原则 263 | > 264 | > 复数的例子和有理数的例子有很多的相似之处,不过最大的不同,也是这里数据驱动的要点,我们的低层实现有两种形式,并且可以相互转换。 265 | > 266 | > 透过这种抽象屏蔽,我们对实际计算方法的选择和实现都可以延后。我们称之为“最小允诺原则”。 267 | 268 | ### 给数据加上 Tag 269 | 270 | 上一节我们对复数的实现的两种方式,都做出了详细的定义,并且通过设计的`最小允诺原则` ,我们可以在上层实现之后再决定选择哪种实现方式,但是这里我们就有了另一个问题,我们可能会希望这两种实现都在系统中出现,并且在有需要的时候进行实时的替换。上一节我们明显没办法直接的作出这样的效果,但是在这一节里我们会通过给不同实现增加 Tag 的方式进行实现。 271 | 272 | 我们可以给不同的实现类型的数据加上 Tag 让系统能知道复数的实现类型,以便于选择正确的 API 对其进行处理。 273 | 274 | 我们可以简单定义如下过程: 275 | 276 | ``` lisp 277 | ; 加标签 278 | (define (attach-tag type-tag contents) (cons type-tag contents)) 279 | ; 返回标签 280 | (define (type-tag datum) 281 | (if (pair? datum) (car datum) 282 | (error "Bad tagged datum -- TYPE-TAG" datum))) 283 | ; 返回数据内容 284 | (define (contents datum) 285 | (if (pair? datum) (cdr datum) 286 | (error "Bad tagged datum -- CONTENTS" datum))) 287 | ; 判断谓词 288 | (define (rectangular? z) (eq? (type-tag z) 'rectangular)) 289 | (define (polar? z) (eq? (type-tag z) 'polar)) 290 | ``` 291 | 292 | 然后我们有了这些判断方法之后就可以分别定义两种不同的 API 实现了,书中为了共存对方法名使用了不同的定义,直角坐标系的 API 添加了 rectangular 的尾缀,极坐标系的 API 添加了 polar 的尾缀。然后再具体实现中对类型进行判断再进行分别处理。 293 | 294 | > Tips 作者在这里不厌其烦的列举了所有改过名字的 API 我再没看完之前都以为作者。。。了 295 | 296 | 这里我们只列举修改过的上层 API 大家就应该都明白什么意思了: 297 | 298 | ``` lisp 299 | (define (real-part z) 300 | (cond ((rectangular? z) (real-part-rectangular (contents z))) 301 | ((polar? z) (real-part-polar (contents z))) 302 | (else (error "Unknown type -- REAL-PART" z)))) 303 | 304 | (define (imag-part z) 305 | (cond ((rectangular? z) (imag-part-rectangular (contents z))) 306 | ((polar? z) (imag-part-polar (contents z))) 307 | (else (error "Unknown type -- IMAG-PART" z)))) 308 | 309 | (define (magnitude z) 310 | (cond ((rectangular? z) (magnitude-rectangular (contents z))) 311 | ((polar? z) (magnitude-polar (contents z))) 312 | (else (error "Unknown type -- MAGNITUDE" z)))) 313 | 314 | (define (angle z) 315 | (cond ((rectangular? z) (angle-rectangular (contents z))) 316 | ((polar? z) (angle-polar (contents z))) 317 | (else (error "Unknown type -- ANGLE" z)))) 318 | ``` 319 | 320 | 我们现在的程序变成了这样,公用的转换部分连接了两种实现: 321 | 322 | ![floor_plus](learn-sicp-3/floorplus.png) 323 | 324 | ### 数据导向与可加性 325 | 326 | > Tips 基于类型指派的系统设计: 327 | > 328 | > 我们在以上的系统中使用的方法,可以被称作 `基于系统指派的系统设计` 。我们定义了具体方法,给出分别不同的实现,然后通过 Tag 进行区分选择对应的方法。但是这种方法也有自己的劣势: 329 | > 330 | > 1. 系统设计是建立在已知系统透明的状态下进行的,我们要对系统中已知的类型一清二楚。 331 | > 2. 命名通过 API 的命名进行区分,无可避免的可能会出现 API 撞名字的情况出现。 332 | 333 | #### 可加性 334 | 335 | 根据之前的讨论,我们可以说以上的设计其实是不具有可加性。所谓的可加性指的是系统的扩充能力。再现有的系统中增加新的 API 是否方便。从现在的情况来看,这明显不方便。我们可以假设我们还有第三种表示方式需要插入,那我们该如何进行呢: 336 | 337 | 1. 我们会再写一遍这些所有的 API 特殊实现(这是正常的) 338 | 2. 然后每个实现的名字都要跟上方法的后缀(烦) 339 | 3. 在对四个实现增加 `cond` 语句的操作(超烦) 340 | 341 | 这里面我们能看到,尤其后两点简直让人无法忍受,实现方法后缀还要能记得住,对实现增加 `cond` 的分支,这里只有4个 API 还好说,但是多了的话就很恐怖的了。 342 | 343 | > Tips 这里缺乏可加性的原因: 344 | > 345 | > 我们在实际的视线中通过 Tag 和 cond 的方式实现了类型和对应方法之间的绑定,但是如果增加类型就要修改代码。所以我们应该试图通过其他方式建立这两种东西之间的相互联系——数据驱动设计 346 | 347 | #### 数据驱动设计 348 | 349 | 这是在分层的抽象屏障之后我们学到的另一种模块化的系统设计技术。抽象屏障让我们把系统分成了层,数据驱动则使用了不同的思路,我们可以通过数据去选择对应的实现和方法。我们先将对应的实现和方法分门别类的去存起来,然后使用接收到的数据去判断和查找我们需要的过程,再使用这些过程去处理数据。因此这种方式被称作数据驱动设计。 350 | 351 | 我们可以用一个表格去表示我们系统所需要的 API: 352 | 353 | | 操作/类型 | Polar | Rectangular | 354 | | ---------- | --------------- | ---------------------- | 355 | | real-part | real-part-polar | real-part-rectangular | 356 | | image-part | imag-part-polar | image-part-rectangular | 357 | | magnitude | magnitude-polar | magnitude-rectangular | 358 | | angle | angle-polar | angle-polar | 359 | 360 | 我们可以用类似这样的一个数据结构去存储我们所需要的 API,使用类型当作第一个维度,操作当作第二个维度进行搜索我们所需要的过程,这样子系统的扩充就不再是一件困难的事情了,我们只需要向这个表格增加一列而已。 361 | 362 | 我们可以假定有这样的 API 进行存储和搜索工作: 363 | 364 | ``` lisp 365 | ; 操作/类型 /过程 366 | (put ) 367 | ; 操作/类型 368 | (get ) 369 | ``` 370 | 371 | > Tips 根据这个 API 可以试试把这个结构想像成一个哈希表 372 | 373 | 既然选择了使用这种方式去构建,我们对两方面的实现就有了一些要求: 374 | 375 | * 两种实现要有同性,能够无缝调用,系统不需要知道具体的实现过程 376 | * 各种实现方法都要有对应的过程实现(可以名称相同了),还要有把数据绑定到表格里的过程 377 | 378 | 直角坐标系: 379 | 380 | ``` lisp 381 | (define (install-rectangular-package) 382 | ; internal procedures 383 | (define (real-part z) (car z)) 384 | (define (imag-part z) (cdr z)) 385 | (define (make-from-real-imag x y) (cons x y)) 386 | (define (magnitude z) 387 | (sqrt (+ (square (real-part z)) (square (imag-part z))))) 388 | (define (angle z) (atan (imag-part z) (real-part z))) 389 | (define (make-from-mag-ang r a) (cons (* r (cos a)) (* r (sin a)))) 390 | 391 | ; interface to the rest of the system 392 | (define (tag x) (attach-tag 'rectangular x)) 393 | 394 | (put 'real-part '(rectangular) real-part) 395 | (put 'imag-part '(rectangular) imag-part) 396 | (put 'magnitude '(rectangular) magnitude) 397 | (put 'angle '(rectangular) angle) 398 | (put 'make-from-real-imag 'rectangular 399 | (lambda (x y) (tag (make-from-real-imag x y)))) 400 | (put 'make-from-mag-ang 'rectangular 401 | (lambda (r a) (tag (make-from-mag-ang r a)))) 402 | 'done) 403 | ``` 404 | 405 | 我们定义了和之前一样的方法,使用了一个无参函数把对应的方法安装到这个表格之中。 406 | 407 | 极坐标包也有类似的东西: 408 | 409 | ``` lisp 410 | (define (install-polar-package) 411 | ;; internal procedures 412 | (define (magnitude z) (car z)) 413 | (define (angle z) (cdr z)) 414 | (define (make-from-mag-ang r a) (cons r a)) 415 | (define (real-part z) (* (magnitude z) (cos (angle z)))) 416 | (define (imag-part z) (* (magnitude z) (sin (angle z)))) 417 | (define (make-from-real-imag x y) 418 | (cons (sqrt (+ (square x) (square y))) (atan y x)) ) 419 | ;; interface to the rest of the system 420 | (define (tag x) (attach-tag 'polar x)) 421 | (put 'real-part '(polar) real-part) 422 | (put 'imag-part '(polar) imag-part) 423 | (put 'magnitude '(polar) magnitude) 424 | (put 'angle '(polar) angle) 425 | (put 'make-from-real-imag 'polar 426 | (lambda (x y) (tag (make-from-real-imag x y)))) 427 | (put 'make-from-mag-ang 'polar 428 | (lambda (r a) (tag (make-from-mag-ang r a)))) 429 | 'done) 430 | ``` 431 | 432 | 在我们把两类处理数据的过程都安装到表格里之后,我们就可以使用方法来进行搜索和调用了: 433 | 434 | ``` lisp 435 | (define (apply-generic op . args) ; . 之后的参数是不定参数 436 | (let ((type-tags (map type-tag args))) ; 求出 args 的 类型 437 | (let ((proc (get op type-tags))) ; 找对应过程 438 | (if proc 439 | (apply proc (map contents args)) ; 应用对应过程 440 | (error 441 | "No method for these types -- APPLY--Generic" 442 | (list op type-tags)))))) 443 | ``` 444 | 445 | 这样子 `复数包` 的 API 就有了无需替换的实现: 446 | 447 | ``` lisp 448 | (define (real-part z) (apply-generic 'real-part z)) 449 | (define (imag-part z) (apply-generic 'imag-part z)) 450 | (define (magnitude z) (apply-generic 'magnitude z)) 451 | (define (angle z) (apply-generic 'angle z)) 452 | ``` 453 | 454 | 即使再有新的 API 加入对应的表格我们的 `复数包` 的 API 也是这些,方法的选择完全依赖数据的类型,这就是数据驱动设计的好处。 455 | 456 | #### 消息传递 457 | 458 | 我们也可以把对象作为一个实体,让实体去接收消息以便于去掉用所需操作的过程。这就是 SmallTalk 中最早的消息传递的 OOP 设计思路。 Scheme 的消息传递设计使用了一个过程表示对象,将对对象的对应操作放在了过程内部。所以说`消息传递` 也被称作 `dynamic dispatching` 或者叫动态分配的技术。 459 | 460 | > Tips OOP? 461 | > 462 | > 谈起 OOP 我们能想到很多东西,在不同的时候我们也会被问到各种和 OOP 有关系的东西。熟悉Java 和 Cpp 的人会说OOP 就是类、想谈 OOP 设计的人可能会说 抽象、封装、继承、多态。但是消息传递作为 OOP 的特性之一我们也要记住。 463 | > 464 | > 还有就是谈 OOP 的时候我们应该确定不同的语义下 OOP 的不同意思。比如 SmallTalk 的 OOP 和 CPP 的 OOP 就有很大区别。 465 | 466 | 我们可以这么来实现一个生成负数的程序: 467 | 468 | ``` lisp 469 | (define (make-from-real-imag x y) 470 | (lambda (op) 471 | (cond ((eq? op 'real-part) x) 472 | ((eq? op 'imag-part) y) 473 | ((eq? op 'magnitude) (sqrt (+ (square x) (square y)))) 474 | ((eq? op 'angle) (atan y x)) 475 | (else 476 | (error "Unknown op--MAKE-FROM-REAL-IMAG" op))))) 477 | ``` 478 | 479 | 看到这个表达式我们就能很清楚的了解到底什么叫 `dynamic dispatching` 了,复数的数据不再使用 `con` 的方法结成 list 而是通过一个方法进行存储,方法对传入的参数进行处理返回对应的部分。 480 | 481 | 我们也要修改 `apply-generic` 过程的具体实现: 482 | 483 | ``` lisp 484 | (define (apply-generic op args) (args op)) 485 | ``` 486 | 487 | 这个实现简直是太简单明了了,我们接收对应的 `op` 和 `arg` ,再把 `op` 传给 `args` 去调用不同的部分。这样每个对象都是一个能够表示状态的 `dispatching` 过程。 488 | 489 | >Tips 这个用过程存储对象的实现其实似曾相识,我们在上一章中就是用过 lambda 表示 `con` 函数。 490 | 491 | ## 带有通用型操作的系统 492 | 在复数包的实现中我们实现了 `直角坐标` 和 `极坐标` 表示方法的统一,我们通过数据驱动的方式动态的去选择我们所需要的方法去对数字进行处理,但是这还是不够,我们的系统可能还想要更高层次的抽象,将有理数、复数、和常规运算三种系统合而为一,让我们的外部 API 只有 `add sub mul div` 这几个函数就好了,这样子整个数字系统的结构图如下所示: 493 | ![system](learn-sicp-3/system.png) 494 | 495 | ### 实现通用的算数运算 496 | 497 | ``` lisp 498 | (define (add x y) (apply-generic 'add x y)) 499 | (define (sub x y) (apply-generic 'sub x y)) 500 | (define (mul x y) (apply-generic 'mul x y)) 501 | (define (div x y) (apply-generic 'div x y)) 502 | ``` 503 | 504 | 这里的技术和上一节的东西没区别,都是数据驱动。我们只需要增加常规计算的 API 进入方法表就可以了。 505 | 506 | ``` lisp 507 | (define (install-scheme-number-package) 508 | (define (tag x) (attach-tag 'scheme-number x)) 509 | (put 'add '(scheme-number scheme-number) 510 | (lambda (x y) (tag (+ x y)))) 511 | (put 'sub '(scheme-number scheme-number) 512 | (lambda (x y) (tag (- x y)))) 513 | (put 'mul '(scheme-number scheme-number) 514 | (lambda (x y) (tag (* x y)))) 515 | (put 'div '(scheme-number scheme-number) 516 | (lambda (x y) (tag (/ x y)))) 517 | (put 'make 'scheme-number (lambda (x) (tag x))) 518 | 'done) 519 | 520 | (define (make-scheme-number n) 521 | ((get 'make 'scheme-number) n)) 522 | ``` 523 | 524 | 我们可以给 Scheme 支持的常规数字面量增加相应的 Tag 和对应的方法。通过这样的方法增加,常规计算就和 `复数` 、`有理数` 一样可以进入这个系统中正常的操作了。复数和有理数的实现和安装方式和之前的没有区别就不赘述了。 525 | 526 | 使用双层 Tag 表示的方法最后会结成一个这样的结构: 527 | 528 | ![rectangle](learn-sicp-3/rectangular.png) 529 | 530 | 首先我们知道这是一个复数,而且知道这是一个用直角坐标表示的,那么我们就可以把这个数字解析成 $ 3 + 4i $ 。 531 | 532 | ### 系统的分层结构和跨越 533 | 534 | 虽然一切似乎都有一种解决了的感觉的,但是我们还有一个非常重要的问题没有去处理。我们目前的系统、系统的 API 的各个部分似乎被拼装到一起了,但是它们实际上还是分开的,复数包的 API 还是只能计算复数之间的运算、常规运算也只能处理常规运算。目前我们并没有实现一种办法去打通各个部分的方式,而且我们的跨类型操作至少需要保证类型隔离不被破坏。 535 | 536 | > Tips 数据类型的跨越 537 | > 538 | > 在分层系统中我们会经常需要对数据进行不同的转型操作,可能会向上转型增加标签,也可能向下转型剥离标签。 539 | 540 | **一种不合理的尝试** : 541 | 542 | ``` lisp 543 | (define (add-complex-to-schemenum z x) 544 | (make-from-real-imag (+ (real-part z) x) 545 | (imag-part z))) 546 | (put 'add '(complex scheme-number) 547 | (lambda (z x) (tag (add-complex-to-schemenum z x)))) 548 | ``` 549 | 550 | 这种通过增加 API 的方法是一种非常不推荐和不合理的尝试,如果每个类型之间的联通都靠增加 API 来实现会造成出 $ O(n^2) $ 个方法出来,简直恐怖。 551 | 552 | #### 层次类型 553 | 554 | 对于本身有明显线形的类型转换系统,我们可以用构建 `类型塔` 这种东西去实现,类型在塔中的向上和向下的类型转换。 555 | 556 | ![type_tower](learn-sicp-3/type_tower.png) 557 | 558 | 每一个更小的数域的数据都可以被更大的数域的数字所概括,所以这时候我们就只需要进行强制向上转型就好了。比如常规 Scheme 数可以看成虚部为 0 的复数,它们与复数之间的运算可以转化为复数运算。如: 559 | 560 | ``` lisp 561 | (define (scheme-number->complex n) 562 | (make-complex-from-real-imag (contents n) 0)) 563 | ; 到一个特殊的强制表(使用 put-coercion 和 get-coercion): 564 | (put-coercion 'scheme-number 'complex 565 | scheme-number->complex) 566 | ``` 567 | 568 | 但是明显有一些类型是不能转化的,比如向下转型就会丢失数据,这样我们就可以不写这些函数(当然如果我们有时候会需要利用这些丢失数据的方法)。 569 | 570 | > Tips 类型塔 571 | > 572 | > 我们在初学 C 语言的时候就已经接触过 类型塔这种东西了 int => float => double 这种东西的实现本质上就是向上转型。另外 Java 中的基本类型的 int to float 数据转换实际上是用过 JNI 层的函数去完成的,使用 `union` 进行数据类型转换这也算是一个技巧吧。 573 | 574 | 相应的我们还需要对 `apply-generic` 过程做一些修改: 575 | 576 | ``` lisp 577 | (define (apply-generic op . args) 578 | (let ((type-tags (map type-tag args))) 579 | (let ((proc (get op type-tag))) 580 | (if proc 581 | (apply proc (map contents args)) 582 | (if (= (length args) 2) 583 | (let (type1 (car type-tags)) ; type 1 584 | (type2 (cadr type-tags)); type 2 585 | (a1 (car args)) ; number 1 586 | (a2 (cadr args))); number 2 587 | (let ((t1->t2 (get-coercion type1 type2)) 588 | (t2->t1 (get-coercion type2 type1))) ; 两种转换方式 一定存在一个 589 | (cond (t1->t2 590 | (apply-generic op (t1->t2 a1) a2)) ; 转换后再进行运算操作 591 | (t2-t1 592 | (apply-generic op a1 (t2->t1 a2))) 593 | (else 594 | (error "No method for these types" 595 | (list op type-tags)))))) 596 | (error "No method for these types" 597 | (list op type-tags)))))) 598 | ``` 599 | 600 | 这种通过 `强制` 的交互操作,我们只需要沿着一条直线进行类型转换就行了,两种类型之间只需要实现一个转换就可以了。运算时将低类型对象逐步强制,直至两对象类型相同,在返回结果时也可以相应的类型下降,下降到无法再下降就可以了(C 中没有类型降解的过程)。 601 | 602 | #### 塔式结构的不足 603 | 604 | 我们对塔式结构的了解和讨论已经足够充分了。但是有的时候我们未免还是太过乐观的了,在实际的使用中,不是所有的东西显示模型都能转成这种类型塔的样子。有更多的情况下,各个类型都可以有可能有各种不同的耦合结构,不会这么的直上直下: 605 | 606 | ![tree](learn-sicp-3/tree.png) 607 | 608 | 就如同图中的这个多边形的例子,我们可能有一个模型具备多种的性质,没有办法进行直上直下的类型塔转化。 609 | 610 | > Tips 符号代数 611 | > 612 | > 书中在这里有一个 `符号代数` 例子,可以参照学习。从中我们可以发现数据导向的程序技术完全能处理任意复杂的递归结构的数据对象,只要一直递归下去就好了。 613 | 614 | ## 总结 615 | 616 | 在这一节中我们学到了很多和数据抽象的知识。在上一章使用过程抽象创建了抽象屏蔽之后,我们能通过数据抽象将实现的选择延迟到使用的时候再进行,结合层次屏蔽一起我们能构建更简洁更健壮的系统。另外我们还见识到了 Scheme 对符号数据计算的支持,这样我们显示定义的符号求导、符号计算成为了可能。在下一章中我们会进一步的看到模块化、求值相关的内容。 -------------------------------------------------------------------------------- /page/learn_sicp_4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0x04:SICP 的魔法 - 模块化、状态、环境 3 | date: 2017-04-23 15:37:48 4 | tags: SICP 5 | --- 6 | 7 | ![cover](learn-sicp-4/cover.jpg) 8 | 9 | 现在我们终于到了 SICP 的第三章的内容了。就个人而言我觉得 SICP 的前三章都在着眼于构建各种层次的抽象系统,三章以后的内容才是 SICP 本身比较精髓和有趣的内容了。但是万丈高楼平地起,正是之前的这些和 “抽象”、“思想” 有关的东西构建了我们在后几章学到的解释器系统。 10 | 11 | 前两章我们见识到了 Scheme 在两个主要方向上的抽象能力。我们在第一章中学到了和过程抽象有关系的知识,我们把他认定为 Scheme 系统的第一公民,看到了各种高阶函数组合起来的魅力。在第二章我们主要讨论的问题是如何进行数据抽象,从一个 “有理数” 的 Demo 入手,建立了抽象屏蔽的系统层次,我们还引入了符号数据,进一步提升了运算的抽象层次,求导等写起来很麻烦的程序都可以借助符号数据来轻松实现。之后我们还见到了通过 dispatch 使用过程保存了对象的状态,学到了使用消息传递的构建方式,等等很多的和 “抽象” 有关的知识。 12 | 13 | 我们确实通过以上的知识,在上一节末构建了一个通用操作的复数系统。但是对于一个更为拟真、更为复杂的系统的时候,以上或是计算、或是对模型抽象的知识就显得远远不足了。我们还应该需要一系列模式或者是说原则去规定去构建这整个系统。换句话说,我们要学习如何去模拟这个世界。 14 | 15 | ![history](learn-sicp-4/history.png) 16 | 17 | > 模拟真实世界? 18 | > 19 | > 很多语言的设计者或是系统的实现者一直致力于让某种编程语言实现的系统去拟真现实世界,从中诞生了很多相关的思想和技术,OOP、OOC 还有各种设计模式都是这种努力的产出。另外很多语言也在致力于语法语义化,试图让编程语言 “看起来” 更像自然语言。 20 | 21 | ## 我们如何模拟世界 22 | 23 | ​ 我们在学习很多 OOP 的语言的时候,都会讲很多 OOP 设计的好处,其中几个优点都有类似的特点,就是说 OOP 实际上是对现实世界的一种模拟,从开发人员的角度来说编写容易思考,而且从系统实现上也比较贴近现实。 24 | 25 | 在实现中我们可以从这两个角度去实现: 26 | 27 | * 我们把现实中的每一种实体抽象为一个对应的程序对象(当然还可能会提取出对对象的抽象:类) 28 | * 把每个现实中实现的具体方法模拟为一个程序中对应的活动。 29 | 30 | 通过对 “对象” 和 “活动” 的拟真,我们就可以用程序去对现实世界进行模拟。 31 | 32 | ### 我们遇到了一些问题 33 | 34 | 可以通过上面的两条去完成从现实到程序的一种转化。但是我们明显发现了一些问题,因为现实世界纷繁复杂,每时每刻的每个实体都在发生着不同的变化,而且每个实体都在发生着不同的动作,相互之间还有大量的交互,如果全部用程序去实现和模拟难以实现。 35 | 36 | 因而我们希望在程序在具体的实现之中,不要有大范围的甚至是全局的数据变化(这不好管理),我们希望把对象的增删修改、活动的产生消亡限定在一个有限的局部内。这样我们的整个系统会被分为不同的小的部分和结构,我们就把整个系统进行了分解的操作。 37 | 38 | > 高内聚和低耦合 39 | > 40 | > 高内聚和低耦合是我们经常听到的设计方式,这样一个使用 `模块化` 的方式,其实是对这种设计思路的一种实践。高内聚是在说模块内聚化,功能内聚在对象之中,只留出相应的接口,使用接口进行交互,降低模块相互的耦合。 41 | 42 | ### 对象的世界 43 | 44 | 从 `对象` 的角度上来看,世界是什么样子的呢?对象的世界本质上是由一大堆对象组成的,对象有自己的属性和状态,随着时间的流逝,对象有一系列的状态的变迁。为此我们要通过一种方式去记录这个状态,通过这个被记录下的状态,我们能表现出这个对象的变化规程,而且还可以通过这个状态去继续计算对象的一系列后续的状态。 45 | 46 | ![timeline](learn-sicp-4/timeline.png) 47 | 48 | 通过以上的这一系列的对 `对象的世界` 的描述,我们可以很容易的发现,我们描述的这种模块化、对象化的设计方式,其实和我们曾经学过的 Cpp、Java、CSharp 的世界非常的相近。我们从上帝的视角,把整个计算系统分解成对每个对象的计算的上去, 用它们模拟真实系统中对象的行为。 49 | 50 | 但是对上面我们提到的那个 `状态的变化` 我们会发现,我们缺少一个我们很熟悉的东西——`赋值` ,下面对对象世界的展开讨论就要从 `赋值` 这个基本操作开始谈。 51 | 52 | ## 赋值和模块化 53 | 54 | `赋值` ,我们刚才怎么才想起来这个东西?回想起来之前两章我们所做的东西,所写的代码,似乎完全忘了赋值这件事(我们对 `define` 的使用和所谓赋值是不同的东西),我们使用了 `代换原则` 就搞定了所有的计算过程,所有的计算都可以被分解为基本数据和过程,根本就没有用上赋值,我们的世界也是闭环的。 55 | 56 | 但是此时潘多拉魔盒终于被打开,我们将引入赋值,昔日仅靠代换形成闭环的田园时代已经不复存在,邪恶的赋值为我们添加了新功能,也为我们带来了很多的麻烦。 57 | 58 | ### 局部状态 59 | 60 | > Tips 新的基本结构: 61 | > 62 | > * begin 63 | > 64 | > ``` lisp 65 | > ; begin 过程会对后面的所有表达式进行求值,然后返回最后一个表达式的值 66 | > (begin ) 67 | > ``` 68 | > 69 | > * set! 70 | > ``` lisp 71 | > ; set! 是一种赋值操作,在 Scheme 中所有对数据进行修改的函数都会使用 ! 尾缀 72 | > ; 可以将一个值付给一个变量名称 73 | > (set! ) 74 | > ``` 75 | 76 | 如果仔细回想我们之前几节中写出的程序,我们似乎对它们的求值顺序并不在意(关于求值顺序我们在前文中提到过两种代换模型的方式:`应用序` 和 `正则序`),因为它是使用 `代换原则` 作为计算模型的程序,我们知道无论它按照什么顺序进行计算,最后都会被分解成基本的形式,所以本身的程序是没有时序关系的。 77 | 78 | 所以我们为什么说赋值破坏了这个模型呢?因为我们的值变成中途可变的了,在这个赋值的动作前后就形成了两个时间点,一个是赋值之前的状态,一个是赋值之后的状态,我们中途的数据变化会影响后续的求值过程,因而 `代换模型` 在这种情况下不再有效了。 79 | 80 | 上面说的是我们引入 `赋值` 要修复计算模型的问题,但是本身我们要让系统模块化还有着新的问题,我们刚才也谈到了我们要保留对象的历史进程,为了保留这个,我们就需要能控制一个对象的内部状态。 81 | 82 | 涉及到局部状态的内容,我们就会发现刚才提到的时序问题就很重要,比如说我们有一个银行账户,里面只有 100 元钱,我们提取一点就少一点,后续的提取一定是根据之前的提取的结果来继续提取,不能说这个东西没有时序,随意的提取。 83 | 84 | 我们需要的结果大概是这个样子的: 85 | 86 | ``` lisp 87 | (withdraw 20) 88 | 80 89 | (withdraw 20) 90 | 60 91 | ``` 92 | 93 | 如果根据直觉的话,我们可能会写出这样的代码: 94 | 95 | ``` lisp 96 | ; 我们写了一个生成 withdraw 的方法 还贴心的考虑到了 balance 作为局部变量传入, 97 | ; 用 lambda 来保存数据状态 98 | (define (withdraw-generator balance) 99 | (lambda (amount) (- balance amount))) 100 | ``` 101 | 102 | 这个方法看起来很不错,我们用到了之前我们学到的知识,使用类似这种 `generator` 的方法去返回一个过程,并且要维护局部变量么,我们用 `lambda` 来维护局部变量也是之前的知识点,不是很好么? 103 | 104 | 但是这个方法明显是不对的,我们实际运行起来只会看到这样的结果: 105 | 106 | ``` lisp 107 | (define withdraw (withdraw-generator 100)) 108 | (withdraw 20) 109 | 80 110 | (withdraw 20) 111 | 80 112 | ``` 113 | 114 | 那么我们知道上文中的代码的一个显著的问题就是状态没办法随着时间的变化而继续的变化,那么我们利用 `lambda` 的闭包性所实现的状态保留也就没有意义了。 115 | 116 | 知道了问题所在我们在修改起代码的时候,就会轻松很多了,只要在每次计算完成的时候,用我们前面提到的 `set!` 方法去实现一次重新赋值就可以解决问题了: 117 | 118 | ``` lisp 119 | (define (withdraw-generator balance) 120 | (lambda (amount) 121 | (if (>= balance amount) 122 | (begin (set! blance (- balance amount)) balance) 123 | "Insufficient funds"))) 124 | ; 我们用 if 做了判断处理,并且使用了 begin 过程,在重新赋值之后返回了新值 125 | ``` 126 | 127 | 我们在进行刚才的尝试就会发现程序和我们的预期的一样了: 128 | 129 | ``` lisp 130 | (define withdraw1 (withdraw-generator 100)) 131 | (define withdraw2 (withdraw-generator 100)) 132 | (withdraw1 20) 133 | 80 134 | (withdraw1 10) 135 | 70 136 | (withdraw2 20) 137 | 80 138 | ``` 139 | 140 | 并且我们使用两个变量去测试的时候我们能分得清这是两个对象,而不是同一段过程的反复调用,两个对象分别都有了自己的历史轨迹。 141 | 142 | ![obj-his](learn-sicp-4/obj-his.png) 143 | 144 | 我们可以扩充一下这个方法,用到我们之前学到的 `dispatch` 方法,把这个方法写得更漂亮: 145 | 146 | ``` lisp 147 | (define (make-account balance) 148 | (define (withdraw amount) 149 | (if (>= balance amount) 150 | (begin (set! balance (- balance amount)) balance) 151 | "Insufficient funds")) 152 | (define (deposit amount) 153 | (set! balance (+ balance amount)) balance) 154 | (define (dispatch m) 155 | (cond ((eq? m 'withdraw) withdraw) 156 | ((eq? m 'deposit) deposit) 157 | (else (error "Unknown req -- MAKE-ACCOUNT" m)))) 158 | dispatch) 159 | ``` 160 | 通过这个方法我们可以通过方法名去使用这个银行账户: 161 | 162 | ``` lisp 163 | (define acc (make-account 100)) 164 | ((acc 'withdraw) 50) 165 | 50 166 | ((acc 'withdraw) 60) 167 | "Insufficient funds" 168 | ((acc 'deposit) 40) 169 | 90 170 | ((acc 'withdraw) 60) 171 | 30 172 | ``` 173 | 174 | > Tips define 和 set! 到底有什么区别: 175 | > 176 | > define 和 set! 都能给一个变量赋值,但是 define 赋值只会定义当前环境内的约束,但是 set! 会考虑到环境的层级问题,并且还有就是 set! 赋值只能赋值一个特定的值,但是 define 的赋值对象则可以是一个 S 表达式。 177 | > 178 | > 这其中和环境赋值有关系的东西在后面还会讲到,这里只要简单记住区别就好。 179 | 180 | ### 赋值带来了? 181 | 182 | #### 一些好处 183 | 184 | 我们引入 `赋值` 自然是有他的好处的,从上面的例子我们很容易的看出通过使用 `赋值` 的方式我们保存了一个对象的状态,这就是系统实现模块化的根基。 185 | 186 | 我们可以再举一个例子去看出 `状态` 对我们有什么帮助,我们想写出一个反复调用都能生成具有统计性质均匀分布的随机序列,我们现在有 `rand-update` 这个方法(可求得随机序列的下一个值)了,我们应该怎么使用它呢? 187 | 188 | ``` lisp 189 | ; 伪代码 190 | x2 = (rand-update x1) 191 | x3 = (rand-update x2) 192 | ``` 193 | 194 | 我们在手动的维护这样的一个随机序列的当前值,并且每次求出了之后我们都会再把这个当前值继续带入拿到下一个值,这么写就不如直接写一个维护当前值状态的生成函数: 195 | 196 | ``` lisp 197 | (define rand 198 | (let ((x random-init)) 199 | (lambda () 200 | (set! x (rand-update x)) 201 | x))) 202 | ``` 203 | 204 | 这样我们真的就省了很多的事情,并且从封装性上来看这也是一种进步,我们的随机数生成进化到一个能维持当前值的过程了。 205 | 206 | 另外如果我们还想说这种 `命令式程序` 的优势在哪,可能就是在表达状态和体现封装上,通过状态的记录,我们很轻松的就能实现一些具体的算法,代码可读性也比较的高,能清晰地反馈算法的本质,但是如果我们试图完全不用任何的状态记录(不使用任何的 `赋值` 语句),我们可能就要写很多的额外代码试图把状态通过参数的方式进行传递,书中通过一个`蒙塔卡罗模拟` 的例子介绍了这部分的知识,这里不再赘述。 207 | 208 | #### 同样多的代价 209 | 210 | 代价我们之前已经有过一些了解了,首先我们的之前在使用的 `代换模型` 没办法继续使用了。代换过程把表达式不断的化简到能把已有的约束带入,这样我们每个表达式的运算结果都是固定的,在这种框架之下我们没办法理解,为什么同样的一个过程运行两次,得到的结果是不同的。 211 | 212 | 为了理解这个问题,我们要理解在 `代换模型` 中,名字实际上只是一个约束、或者说是值的代号,因而我们才能通过代换消除名字进行计算(包含过程体)。但是我们在有赋值之后,名字和约束就不是一种能够进行等价替换的东西了,在这种情况下,名字我们更倾向把它理解成一种存储 `值` 的位置,或者说它是指向某一个存储 `值` 位置的一种指针,我们可以通过这个指针去访问、修改对应位置的一个 `值` 。但副作用就是指针不代表这个值,我们每次使用 `值` 的时候需要通过这个指针去访问。 213 | 214 | 在这种计算情况之下,变化的不只是赋值导致了简单计算模型的失效,这种变化还带来了更深远的影响,借由 "名字不再能表示一个值" 这个现实,我们开始思考程序中**同一性**的问题。 215 | 216 | ### 同一性产生了变化 217 | 218 | 我们说一种语言支持 **同样的东西可以相互替换** ,而且这种替换不会改变表达式的值(程序的意义),称这种语言具有 **引用透明性**。在引入赋值之前,我们一直编写的程序都拥有 **引用透明性**。当我们引入赋值,程序失去程序的透明性之后,程序中对 "同一性" 的定义变得越来越复杂了。 219 | 220 | 赫拉克利特说:“人不可能两次踏进同一条河流”,这里对同一条的河流的定义就明显增加了对其中的河水的定义,因为河水一直在不断地流淌,参照我们引入赋值出现的对象的时间点问题,所以我们就不能认为这是一个同一条河流、同一个对象。 221 | 222 | 因而赋值打破了语言的 **引用透明性**: 223 | 224 | * `同样东西` 的概念不能通过形式化的描述直接确定 225 | * 替换对程序的影响越来越难判定 226 | 227 | 我们来举一个例子来展示同一性产生的变化,比如 **Paul** 和 **Peter** 有银行账户,其中有 **100** 块钱。我们针对这种模拟有两种写法大家可以从这两种模拟的结果之中看出同一性变化带来的影响: 228 | 229 | 第一种模拟: 230 | 231 | ``` lisp 232 | (define peter-acc (make-account 100)) 233 | (define paul-acc (make-account 100)) 234 | ``` 235 | 236 | 另一种模拟: 237 | 238 | ``` lisp 239 | (define peter-acc (make-account 100)) 240 | (define paul-acc peter-acc) 241 | ``` 242 | 243 | **Paul** 和 **Peter** 在最开始使用的时候,都会发现自己的账户里面有 **100** 元钱,但是最后他们分别得提取或是充入钱,会让他们发现两种情况是不同的,在第一种模拟之下他们那使用的是不同的两个账户,但是在另一种模拟之下,他们使用的是同一个账户,但是我们在不具体使用之前,程序没有任何的表示我们这两个账户是共享和共通的。 244 | 245 | > Tips eq? 246 | > 247 | > 我们在很早的时候就接触过和同一性有关的东西,我们说过`eq?` 这个过程判断的是对象是否是同一的,而 `equal?` 检查的却是字面上的程序是否相等。 248 | 249 | ### 命令式程序的缺陷 250 | 251 | 我们会把基于 `赋值` 所构建的程序设计称为 `命令式程序设计`。基于命令式设计的程序由于无法使用代换模型,需要使用更为复杂的计算模型去解释,因而也很容易出现一些依赖时序性的问题。 252 | 253 | 我们举一个迭代式求阶乘过程的程序: 254 | 255 | ``` lisp 256 | (define (factorial n) 257 | (define (iteritor product counter) 258 | (if (> counter n) 259 | product 260 | (iteraotr (* counter product) (+ counter 1)))) 261 | (iterator 1 1)) 262 | ``` 263 | 264 | 如果我们仍然用 Scheme 去实现这个求阶乘的过程,我们通过赋值去直接修改 `prudect` 和 `counter` 的数值,而不是使用参数传递: 265 | 266 | ``` lisp 267 | (define (facatorial n) 268 | (let ((product 1) 269 | (counter 1)) 270 | (define (iterator) 271 | (if (> counter n) 272 | product 273 | (begin (set! product (* counter product)) ; 用赋值代替传递参数 274 | (set! counter (+ counter 1)) 275 | (iterator)))) 276 | (iterator))) 277 | ``` 278 | 279 | 但是这里面我们很明显的会发现,我们对 **counter** 的计算一定要放在 **product** 值计算的后面,如果这个顺序颠倒了的话,我们先更新了 counter 的计算,那依赖 counter 计算的 product 就会出现计算异常。 280 | 281 | ## 环境 282 | 283 | 我们在上一节中已经讨论过了,`代换模型` 不再适用,我们的 `名字` 不再是某个值的 `别名` 了,而是一个存储固定位置的指针,那我们就可以引入一种 `环境模型` 去解决一个问题,再很久以前的章节中,我们说过了 `环境` 本质上是一个存在与局部的表格(我们通常能通过各种 **vector** 和 **map** 去做具体实现),我们的 `名字` 和 `值` 存储于其中,通过对 `名字` 的访问、修改完成环境模型的调用。 284 | 285 | ![env](learn-sicp-4/environment.png) 286 | 287 | 观察上图,我们的环境就是由层层的环境层次嵌套而得到的,我们针对每个内部环境都有一个环境,然后去指向挖补环境。由此可见,我们的环境非常重要,确定了新的求值模型的新的上下文条件,即使是我们之前写的代码也要依赖环境模型: 288 | 289 | ``` lisp 290 | (+ 1 1) 291 | ``` 292 | 293 | 即使是这样简单的数据相加,我们也要在环境中实现提供 **加法+** 的具体实现才能进行正常的求值和计算。 294 | 295 | > Tips 解释器的构造 296 | > 297 | > 解释器的运行方式和环境模型有很大的关系,通过包含一个全局环境,再在运行时针对过程不断地创建新的环境并求值。 298 | 299 | ### 环境的求值 300 | 301 | 环境的求值规则变化也不是很大: 302 | 303 | * 求出组合式各个子表达式的值 304 | * 将运算符表达式的值赋给对象表达式的值 305 | * 用 `set!` 和 `define` 修改和绑定环境约束 306 | * 求值过程中环境逐渐变化 307 | 308 | > Tips 这里书中颇费笔墨的描述了很多个例子的具体的环境求值,我个人觉得这里只需要明白原理那些具体过程就好分析很多了。这里我们只举一个例子,从头到尾,从一个解释器的角度去看环境的求值模型到底是怎么回事。 309 | 310 | ![eval](learn-sicp-4/eval.png) 311 | 312 | 这里我们略过了词法分析,杜撰了一棵已经被整理好的 AST 树,我们假设有这么一个 **解释器** 针对这一个 AST 进行求值。 313 | 314 | #### 环境的绑定 315 | 316 | 首先我们的**解释器**接到了这棵 AST 树,然后我们从首层开始遍历去处理。 317 | 318 | 首先我们的解释器获取的是 **define** 节点和获取到它对应的 **name** :squash,这时我们要进行的就是对本过程进行绑定。这时候我们就会在全局环境的 **表格** 之中插入一个对应项 squash,我们只需要给这个项目一个内部环境就可以啦,这个环境里我们不需要详细的对 lambda 部分进行展开,我们在其中保存好这个 AST 的结构就可以了,这样我们就可以在求值的时候,针对这个求值了。 319 | 320 | > 绑定之后的场景就像是之前的图片一样 321 | 322 | #### 过程的应用 323 | 324 | 我们现在已经完成了过程在环境中的绑定问题,我们现在要在实际的运算中使用我们的这个过程,比如我们在过程中写出这样的代码: 325 | 326 | ``` lisp 327 | (squash 5) 328 | ``` 329 | 330 | 我们首先会创建一个新的求值环境,因为我们每次的程序调用都是一个独立的求值过程,因此我们可以在这个节点里面建立一个新的求值环境,新的环境的外围环境就是之前绑定的这个过程。 331 | 332 | ![env-eval](learn-sicp-4/eval-env.png) 333 | 334 | 在新环境中我们要求值一个 lambda 表达式: 335 | 336 | * 建立一个过程对象 337 | * 其代码是该 lambda 表达式的过程体和参数 338 | * 其环境指针指向 New env 339 | 340 | > Tips 我们再来看看 define 和 set! 的不同 341 | > 342 | > * define 会添加当前框架的一个约束,而如果这个框架已经包含了这个约束的话,会对这个约束进行修改(参照具体的实现) 343 | > * set!会在当前环境里查找 的约束。如果当前框架里有,约束就确定了;否则到外围框架去找。查找可以沿外围环境指针前进多步,把找到的约束中变量的约束值修改为由 算出的值如果环境中没有<变量> 的约束(查找到达全局框架但仍未找到),就报告变量无定义错误。 344 | 345 | ### 其他环境相关 346 | 347 | 和求值环境相关的内容,我们在之前就已经进行了很多的铺垫,所以这里的介绍就比较简略。简单的环境求值模型也比较的好理解。书中还提到了使用二次表格记录两个层次数据的求值模型、包含内部定义的层次模型等多种应用,其实就是使用两个维度的表格啊、在环境的内部中再嵌套一层环境啊这样子的东西。 348 | 349 | ## 小结 350 | 351 | SICP 的这章和之前讨论的东西有一些不同,第一二章讨论的内容都是利用过程、数据等各种的抽象手段去模拟具体的事物。但是第三章上升了一个层次,我们在这章站在了一个更为宏观的角度去看整个世界的模拟,在这一节里我们做出了非常大的改变,我们抛弃了原来的代换模型,引入了新的环境模型,赋值的加入使我们的程序的数据可以随时改变,这造成了一些困扰也增长了我们的程序表现力。 352 | 353 | 这个系列的文章我忽略了一些的书中提供的 demo ,只突出了一些需要思考的重点,毕竟我对这个系列的文章的期许是能让 SICP 中的思想用我的文字根据我自己的理解表达出来,而不是试图把书完完整整的抄一遍。 SICP 作为一个编程的入门级书籍有很强的思想性和指导性,我希望这个系列文章能帮助更多入门的同学。毕竟即使是程序员社区也包含着各种戾气、鄙视链和莫名的民科和自大,希望入门的同学们能在入门的时候就能多接触这类有趣又有用的好书(提高自己的姿势水平 354 | 355 | 这篇坑的时间确实有点长了,主要是日常还有很多的事情,博主目前还在大三前一阵去搞定找实习什么的(虽然好像也没付出啥努力),总之这篇在每天写一点的状态下还是写完啦。接下来的日子虽然也不会太闲,但是我尽量安排好自己写东西的时间好啦。 -------------------------------------------------------------------------------- /page/learn_sicp_5.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 0x05:SICP 的魔法 - 实例:数字电路模拟 3 | date: 2017-08-08 00:16:16 4 | tags: SICP 5 | --- 6 | 7 | ![cover](learn-sicp-5/cover.jpg) 8 | 9 | 在第二章我们学到的和 **数据抽象** 相关的知识指出,如果想构造数据抽象,我们需要两个部分: 10 | 11 | 1. 创建构造函数包含数据 12 | 2. 创建选择函数 **分派** 数据 13 | 14 | 但是在经过了解了第三章相关的 *模块化、状态、环境* 的知识之后,我们认识到了新的问题,在实际编程之中,需要依赖程序的状态进行编程,那么程序中就要根据我们的环境求值的方式进行计算,那我们在重新设计和模拟系统大的时候就要多考虑几点了: 15 | 16 | 3. 在系统中我们带状态的数据抽象 17 | 4. 创建 **改变函数(mutator)** 去对数据进行重新修改 18 | 19 | 20 | ## 基于变动的模拟 21 | 22 | 在构建复杂的系统之中,我们最先面对的部分就是关于 *同一性* 的知识,这部分知识我们已经在上一章的 **同一性发生了变化** 的那个小节中简单的讨论过一次,我们可以在这个再重新讨论一下 **共享和相等** 的知识。 23 | 24 | ### 共享和相等 25 | 26 | 我们通过引入赋值的方式为系统引入了状态,但是造成了引用透明的危机,我们没办法再通过相同的结构来判断对象西相同,两个相同结构的对象并不能确定两个对象是否相同: 27 | 28 | ``` lisp 29 | (define x (list 'a 'b)) 30 | (define z1 (cons x x)) 31 | ``` 32 | 33 | 我们定义了这样的一个结构,*x* 是`'a` 和 `'b` 组成的序对,然后 *z1* 是由两个 *x* 的组成的序对,我们还要在另外定义一个 *z2*: 34 | 35 | ``` lisp 36 | (define z2 (cons (list 'a 'b) (list 'a 'b))) 37 | ``` 38 | 39 | 这两个结构的定义起来,看起来的结构是一样的: 40 | 41 | ![struct](learn-sicp-5/struct.png) 42 | 43 | 这里我们能看到放置 `'a` 和 `'b` 两个序对中的节点都指向了同一个节点,这是因为在 Scheme 中符号引用是共享的,因而他们都指向了同一个节点。但是很明显虽然 **符号引用** 都指向了同一个节点,但是整体的结构是两个结构指向了两个结构,直接使用: 44 | 45 | ``` lisp 46 | (eq? z1 z2) 47 | ``` 48 | 49 | 的结果肯定是不相等的,这意味着我们每次使用 `cons` 方法结成一个新的序对都是生成了一个新的对象并返回的是可操作该对象的 *指针/引用* (虽然 Scheme 中没有显示的这种概念)。 50 | 51 | 这时候我们需要考虑另一个问题,就是在整个 *数据抽象* 都是不可变的情况下,我们对于数据结构是否使用了同样的引用是不可知的,但是当我们引入 *赋值* 我们共享的数据因为使用了同一个贡献的结构而变得复杂起来,修改其中的一个会导致另一个收到影响。 52 | 53 | 比如我们上文中提到的 *z1* 和 *z2* 就是这样结构,z1 的两个子结构全都是 x ,如果我们修改一个另一个肯定会受到影响,但是 z2 序对中的两个元素是两个不同的序对,并不会有这个的问题。 54 | 55 | 引入共享结构会带来一些好处: 56 | 57 | * 扩充数据抽象能够定义的数据范围 58 | * 作为内部状态引入的结构,能够在程序的运行过程中不断地变化,这就可以承担我们需要让他们模拟更为复杂可变的需求 59 | 60 | 但是上面的这个例子(关于 z1 和 z2 的)也表明了,我们在构建复杂结构的时候需要自己清楚那些结构是共享的,在对这些共享的进行修改的时候我们也要谨慎。 61 | 62 | ### 以赋值改变结构 63 | 64 | > Tips 基于改变函数的新 API,我们的数据依赖 **序对** 进行构造,那我们的改造函数需要和构造函数类似的结构 65 | > 66 | > * `set-car!` 修改序对中的首项 67 | > * `set-cdr!` 修改序对中的第二项 68 | 69 | 70 | 在之前我们使用该通过过程来存储数据和模拟序对的表示,当时我们通过 $0,1$ 作为 Tag 模拟了序对的分派,这里我们可以对这个程序略作修改: 71 | 72 | ``` lisp 73 | (define (cons x y) 74 | (define (dispatch m) 75 | (cond ((eq? m 'car) x) 76 | ((eq? m 'cdr) y) 77 | (else (error "Undefined operation -- cons" m)))) 78 | dispatch) 79 | 80 | (define (car z) (z 'car)) 81 | (define (cdr z) (z 'cdr)) 82 | ``` 83 | 84 | 这个程序的变化其实不大,我们这是利用了 Scheme 中的符号引用的唯一性的这点,本质上和用 **0 1** 没有什么区别。我们再跟着这个改造的理念可以为这个程序进行扩充,添加我们之前提到的 `set-car!` 和 `set-cdr!` 函数: 85 | 86 | ``` lisp 87 | (define (cons x y) 88 | (define (set-x! v) (set! x v)) 89 | (define (set-y! v) (set! y v)) 90 | (define (dispatch m) 91 | (cond ((eq? m 'car) x) 92 | ((eq? m 'cdr) y) 93 | ((eq? m 'set-car!) set-x!) 94 | ((eq? m 'set-cdr!) set-y!) 95 | (else (error "Undefined operation -- cons" m)))) 96 | dispatch) 97 | 98 | (define (car z) (z 'car)) 99 | (define (cdr z) (z 'cdr)) 100 | (define (set-car! z new-value) ((z 'set-car!) new-value) z) 101 | (define (set-cdr! z new-value) ((z 'set-cdr!) new-value) z) 102 | ``` 103 | 104 | 这里面我们通过增加了程序的内部状态,并且增加了两个内部方法来搞定了这个问题。 105 | 106 | 接下来部分是几个非常有趣的实例学习,包括对表格,队列和数字电路的模拟。这里面前面的表格、队列的知识都比较的简单,这里我们主要关注这个 *数字电路模拟* 的学习。 107 | 108 | ## 数字电路模拟 109 | 110 | > 本节中的代码可以到 Github 仓库 [**SICP-Magical-Book**](https://github.com/lfkdsk/SICP-Magical-Book) 中获取 111 | 112 | ![plus](learn-sicp-5/plus.gif) 113 | 114 | 这是一个比较有趣的实例,一个数字电路的模拟器本身是完成一种基于状态的系统模拟,而且本身从设计构建系统的角度中来看数字电路的模拟也是非常好玩,因为本身数字电路是由更多的细小的部件来构成的,本身带有一种自底向上进行程序设计的思路。另外,数字电路模拟还包含了另一种思想,一种基于事件驱动的模拟系统,我们用 *事件信号* 模拟实际的 *数字信号* 流过整个模拟的电路程序。 115 | 116 | ### 事件驱动设计 117 | 118 | > **事件驱动程序设计**(英语:**Event-driven programming**)是一种电脑[程序设计](http://www.wikiwand.com/zh-sg/%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88)[模型](http://www.wikiwand.com/zh-sg/%E6%A8%A1%E5%9E%8B)。这种模型的程序运行流程是由用户的动作(如[鼠标](http://www.wikiwand.com/zh-sg/%E6%BB%91%E9%BC%A0)的按键,键盘的按键动作)或者是由其他程序的[消息](http://www.wikiwand.com/zh-sg/%E8%A8%8A%E6%81%AF)来决定的。相对于批处理程序设计(batch programming)而言,程序运行的流程是由[程序员](http://www.wikiwand.com/zh-sg/%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88%E5%B8%AB)来决定。批量的程序设计在初级程序设计教学课程上是一种方式。然而,事件驱动程序设计这种设计模型是在交互程序(Interactive program)的情况下孕育而生的。 119 | > 120 | > —— Wikipedia 121 | 122 | 从这段维基百科中摘取出来的关于 *事件驱动程序* 的介绍,我们对事件驱动可能已经有了一定的理解。其实简单的去理解事件驱动设计,其实就是和传统的基于批处理的程序设计不同,因为那么去写过程式的程序,只能根据程序开发者的编写顺序对程序进行运行,但是事件驱动的程序设计,就像是设定了某些条件,当某些事件触发了这些条件的时候,程序会自动对事件进行处理。 123 | 124 | 这里我们的数字电路的模拟也是类似,一个数字电路由更细粒度的一系列的组件构成,在整个数字电路的操作中,各个部件的活动构成了这个整体的活动。 125 | 126 | * 电路系统的构建的过程中会遇到一些事件,这些事件是由某些部件遇到一些情况而引发的,并且引发可能是有时间顺序的(很正常,数字信号的传播肯定有顺序)。 127 | * 还有一个是事件会引起状态的继续改变,状态的改变又会继续带来事件的产生和传播。 128 | 129 | ### 基础部件 130 | 131 | 实际的数字电路线路由一些电子元件和它们之间的连线组成,一个电子元件可能有几个输入端口和一个输出端口,功能就是从多个接入信号经过一系列处理然后输出一个信号: 132 | 133 | ![component](learn-sicp-5/and.png) 134 | 135 | 就向我们上图的这些部件,可以提供一些 *与、或、非门* 这些基础部件,另外我们还需要各种部件之间的连线,这些连线会需要传递 *0、1* 这样的信号,除此之外各种功能块都会有不同的输出并且产生删除出信号都会有一些信号延迟。 136 | 137 | 在不断地通过基本构造块和连线进行数字链路的搭建的过程中,实际的数字电路程序已经实际成了一种结构设计语言,并且通过模块的不断构造我们语言的中使用的基本模块也在不断的在扩充,我们用这种语言能够构造结构任意复杂的数字电路: 138 | 139 | * **模块化** —— 基本构造块是基本元素 140 | * **组合机制** —— 通过基本元素和连线进行组合 141 | * **抽象机制** —— 我们可以进一步将复杂的组合过程抽象成过程 142 | 143 | ### 连线 144 | 145 | > Tips 半加器: 146 | > 147 | > 半加器的功能是将两个一位二进制数相加。它具有两个输入和两个输出(分别是和、进位)。输出的进位信号代表了输入两个数相加溢出的高一位数值。因此,这两2个一位二级制数的和等于2*C* + *S*。根据两个一位二进制数相加的结果,可以通过真值表、[卡诺图](https://zh.wikipedia.org/wiki/%E5%8D%A1%E8%AF%BA%E5%9B%BE)得到右图所描绘的简易半加器设计。它使用了一个[异或门](https://zh.wikipedia.org/wiki/%E5%BC%82%E6%88%96%E9%97%A8)来产生和*S*,并使用了一个[与门](https://zh.wikipedia.org/wiki/%E4%B8%8E%E9%97%A8)来产生进位信号*C*。如果再添加一个[或门](https://zh.wikipedia.org/wiki/%E6%88%96%E9%97%A8)来接收低位的进位输出信号,则两个半加器就构成了一个全加器。 148 | 149 | 电路需要由连线来连接我们的基础功能块,由基础的功能块组成更大的功能块,我们这里先使用一个定义生成基础连线的方法 *(make-wire)* 生成一条连线,我们可以通过这个方法生成连线,然后把各个部件绑定在一起: 150 | 151 | ``` lisp 152 | ; 生成六根连线 153 | (define a (make-wire)) 154 | (define b (make-wire)) 155 | (define c (make-wire)) 156 | (define d (make-wire)) 157 | (define e (make-wire)) 158 | (define s (make-wire)) 159 | 160 | ; 使用与或非门构建半加器 参考之前的半加器图片 161 | (or-gate a b d) 162 | (and-gate a b c) 163 | (inverter c e) 164 | (and-gate d e s) 165 | ``` 166 | 167 | 这里面的 A 和 B 是数据输入,S 和 C 是数据输出,其中S 是和,C 是进位,我们能看到抽象出来连接线、和与、或、非门之后我们对电路的的描述能被抽象到非常简洁形式,我们还可以把这几个过程结合成一个流程生成出一个 半加器的过程: 168 | 169 | ``` lisp 170 | (define (half-adder a b s c) 171 | (let ((d (make-wire)) 172 | (e (make-wire))) 173 | (or-gate a b d) 174 | (and-gate a b c) 175 | (inverter c e) 176 | (and-gate d e s) 177 | 'ok)) 178 | ``` 179 | 180 | 两个半加器和一个或门能够成一个全加器: 181 | 182 | ![full-adder](learn-sicp-5/adder.png) 183 | 184 | ``` lisp 185 | (define (full-adder a b c-in sum c-out) 186 | (let ((s (make-wire)) 187 | (c1 (make-wire)) 188 | (c2 (make-wire))) 189 | (half-adder b c-in s c1) 190 | (half-adder a s sum c2) 191 | (or-gate c1 c2 c-out) 192 | 'ok)) 193 | ``` 194 | 195 | 这就是像是我们之前提到的,使用抽象机制将全加器、半加器这些复杂组件封装成构建过程。 196 | 197 | 我们在这里还可以给出连线上的发送信号的基本操作: 198 | 199 | ``` lisp 200 | (get-signal ) ; 返回当前线上的信号值 201 | (set-signal! ) ; 重设线上的信号值 202 | (add-action! ) ; 在线上的信号改变的时候运行过程 203 | (after-delay