├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── rust-toolchain.toml └── src ├── ADT.md ├── Algeff.md ├── CHIso.md ├── ChurchE.md ├── CoData.md ├── Continuation.md ├── GADT.md ├── HKT.md ├── Lifting.md ├── Monad.md ├── Monoid.md ├── StateMonad.md ├── TableDriven.md └── lib.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [ push, pull_request ] 2 | name: Test 3 | 4 | jobs: 5 | lint: 6 | name: Lint 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | name: Checkout 🛎️ 11 | - uses: Swatinem/rust-cache@v1 12 | - uses: actions-rs/toolchain@v1 13 | name: Setup Cargo Toolchain 🛎️ 14 | with: 15 | components: rustfmt, clippy 16 | toolchain: nightly 17 | default: true 18 | - uses: actions-rs/cargo@v1 19 | name: Generate Code 🚀 20 | with: 21 | command: build 22 | args: --workspace 23 | - uses: actions-rs/cargo@v1 24 | name: Run Clippy Lints 🔨 25 | with: 26 | command: clippy 27 | args: --all-targets --all-features 28 | 29 | test: 30 | name: Test 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | name: Checkout 🛎️ 35 | - uses: Swatinem/rust-cache@v1 36 | - uses: actions-rs/toolchain@v1 37 | name: Setup Cargo Toolchain 🛎️ 38 | with: 39 | profile: minimal 40 | toolchain: nightly 41 | default: true 42 | - uses: actions-rs/cargo@v1 43 | name: Running Tests 🚀 44 | with: 45 | command: test 46 | args: --workspace -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /src/*.rs 3 | /src/lib.md 4 | !/src/lib.rs 5 | /tango.stamp 6 | /.idea -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "cfg-if" 5 | version = "0.1.10" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 8 | 9 | [[package]] 10 | name = "compile-fail" 11 | version = "0.1.0" 12 | source = "git+https://github.com/rylev/compile-fail#522b21ae5a867329b9e055b5bbd83298191b3113" 13 | dependencies = [ 14 | "proc-macro2", 15 | "quote", 16 | "syn", 17 | ] 18 | 19 | [[package]] 20 | name = "filetime" 21 | version = "0.1.15" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" 24 | dependencies = [ 25 | "cfg-if", 26 | "libc", 27 | "redox_syscall", 28 | ] 29 | 30 | [[package]] 31 | name = "idna" 32 | version = "0.1.5" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 35 | dependencies = [ 36 | "matches", 37 | "unicode-bidi", 38 | "unicode-normalization", 39 | ] 40 | 41 | [[package]] 42 | name = "kernel32-sys" 43 | version = "0.2.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 46 | dependencies = [ 47 | "winapi", 48 | "winapi-build", 49 | ] 50 | 51 | [[package]] 52 | name = "libc" 53 | version = "0.2.80" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" 56 | 57 | [[package]] 58 | name = "magic-in-ten-mins-rs" 59 | version = "0.1.0" 60 | dependencies = [ 61 | "compile-fail", 62 | "tango", 63 | ] 64 | 65 | [[package]] 66 | name = "matches" 67 | version = "0.1.8" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 70 | 71 | [[package]] 72 | name = "percent-encoding" 73 | version = "1.0.1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 76 | 77 | [[package]] 78 | name = "proc-macro2" 79 | version = "1.0.24" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 82 | dependencies = [ 83 | "unicode-xid", 84 | ] 85 | 86 | [[package]] 87 | name = "quote" 88 | version = "1.0.7" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 91 | dependencies = [ 92 | "proc-macro2", 93 | ] 94 | 95 | [[package]] 96 | name = "redox_syscall" 97 | version = "0.1.57" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 100 | 101 | [[package]] 102 | name = "same-file" 103 | version = "0.1.3" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" 106 | dependencies = [ 107 | "kernel32-sys", 108 | "winapi", 109 | ] 110 | 111 | [[package]] 112 | name = "syn" 113 | version = "1.0.53" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" 116 | dependencies = [ 117 | "proc-macro2", 118 | "quote", 119 | "unicode-xid", 120 | ] 121 | 122 | [[package]] 123 | name = "tango" 124 | version = "0.8.0" 125 | source = "git+https://github.com/pnkfelix/tango.git#2f1a76851cea0a4d5c66326268bfdb7daad4322e" 126 | dependencies = [ 127 | "filetime", 128 | "url", 129 | "walkdir", 130 | ] 131 | 132 | [[package]] 133 | name = "tinyvec" 134 | version = "1.1.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" 137 | dependencies = [ 138 | "tinyvec_macros", 139 | ] 140 | 141 | [[package]] 142 | name = "tinyvec_macros" 143 | version = "0.1.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 146 | 147 | [[package]] 148 | name = "unicode-bidi" 149 | version = "0.3.4" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 152 | dependencies = [ 153 | "matches", 154 | ] 155 | 156 | [[package]] 157 | name = "unicode-normalization" 158 | version = "0.1.16" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" 161 | dependencies = [ 162 | "tinyvec", 163 | ] 164 | 165 | [[package]] 166 | name = "unicode-xid" 167 | version = "0.2.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 170 | 171 | [[package]] 172 | name = "url" 173 | version = "1.7.2" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 176 | dependencies = [ 177 | "idna", 178 | "matches", 179 | "percent-encoding", 180 | ] 181 | 182 | [[package]] 183 | name = "walkdir" 184 | version = "1.0.7" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" 187 | dependencies = [ 188 | "kernel32-sys", 189 | "same-file", 190 | "winapi", 191 | ] 192 | 193 | [[package]] 194 | name = "winapi" 195 | version = "0.2.8" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 198 | 199 | [[package]] 200 | name = "winapi-build" 201 | version = "0.1.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 204 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "magic-in-ten-mins-rs" 3 | version = "0.1.0" 4 | authors = ["PhotonQuantum "] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | [dependencies] 9 | compile-fail = { git = "https://github.com/rylev/compile-fail" } 10 | 11 | [build-dependencies] 12 | tango = { git = "https://github.com/pnkfelix/tango.git" } 13 | 14 | [lib] 15 | name = "magic_in_ten_mins_rs" 16 | path = "src/lib.rs" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public licenses. 379 | Notwithstanding, Creative Commons may elect to apply one of its public 380 | licenses to material it publishes and in those instances will be 381 | considered the “Licensor.” The text of the Creative Commons public 382 | licenses is dedicated to the public domain under the CC0 Public Domain 383 | Dedication. Except for the limited purpose of indicating that material 384 | is shared under a Creative Commons public license or as otherwise 385 | permitted by the Creative Commons policies published at 386 | creativecommons.org/policies, Creative Commons does not authorize the 387 | use of the trademark "Creative Commons" or any other trademark or logo 388 | of Creative Commons without its prior written consent including, 389 | without limitation, in connection with any unauthorized modifications 390 | to any of its public licenses or any other arrangements, 391 | understandings, or agreements concerning use of licensed material. For 392 | the avoidance of doubt, this paragraph does not form part of the public 393 | licenses. 394 | 395 | Creative Commons may be contacted at creativecommons.org. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习 (Rust) 2 | 3 | 改写自 [十分钟魔法练习-玩火](https://github.com/goldimax/magic-in-ten-mins) 4 | 原版为 Java 实现 5 | 6 | 另有 7 | [C++版-图斯卡蓝瑟](https://github.com/tusikalanse/magic-in-ten-mins-cpp) | 8 | [C#版-CWKSC](https://github.com/CWKSC/magic-in-ten-mins-csharp) 9 | 10 | 抽象与组合 11 | 12 | 希望能在十分钟内教会你一样魔法 13 | 14 | QQ群:1070975853 | 15 | [Telegram Group](https://t.me/joinchat/HZm-VAAFTrIxoxQQ) 16 | 17 | > 目录中方括号里的是前置技能。 18 | 19 | ## 测试所有用例 20 | 21 | ``` shell script 22 | $ cargo test 23 | ``` 24 | 25 | *MSRV: Nightly* 26 | 27 | ## 类型系统 28 | 29 | [偏易|代数数据类型(Algebraic Data Type)[Rust 基础]](src/ADT.md) 30 | 31 | [偏易|广义代数数据类型(Generalized Algebriac Data Type)[Rust 基础,ADT]](src/GADT.md) 32 | 33 | [偏易|余代数数据类型(Coalgebraic Data Type)[Rust 基础,ADT]](src/CoData.md) 34 | 35 | [偏易|单位半群(Monoid)[Rust 基础]](src/Monoid.md) 36 | 37 | [较难|高阶类型(Higher Kinded Type)[Rust 基础]](src/HKT.md) 38 | 39 | [中等|单子(Monad)[Rust 基础,HKT]](src/Monad.md) 40 | 41 | [较难|状态单子(State Monad)[Rust 基础,HKT,Monad]](src/StateMonad.md) 42 | 43 | [中等|简单类型 λ 演算(Simply-Typed Lambda Calculus)[Java 基础,ADT,λ 演算]](doc/STLC.md) 44 | 45 | [中等|系统 F(System F)[Java 基础,ADT,简单类型 λ 演算]](doc/SystemF.md) 46 | 47 | [中等|系统 Fω(System Fω)[Java 基础,ADT,系统 F]](doc/SysFO.md) 48 | 49 | [较难|构造演算(Calculus of Construction)[Java 基础,ADT,系统 Fω]](doc/CoC.md) 50 | 51 | [偏易|π 类型和 Σ 类型(Pi type & Sigma type)[ADT,构造演算]](doc/PiSigma.md) 52 | 53 | ## 计算理论 54 | 55 | [较难|λ演算(Lambda Calculus)[Java基础,ADT]](doc/Lambda.md) 56 | 57 | [偏易|求值策略(Evaluation Strategy)[Java基础,λ演算]](doc/EvalStrategy.md) 58 | 59 | [较难|丘奇编码(Church Encoding)[λ 演算]](src/ChurchE.md) 60 | 61 | [很难|斯科特编码(Scott Encoding)[构造演算,ADT,μ](doc/ScottE.md) 62 | 63 | [中等|Y 组合子(Y Combinator)[Java 基础,λ 演算,λ 演算编码]](doc/YCombinator.md) 64 | 65 | [中等|μ(Mu)[Java 基础,构造演算, Y 组合子]](doc/Mu.md) 66 | 67 | [中等|向量和有限集(Vector & FinSet)[构造演算, ADT ,依赖类型模式匹配]](doc/VecFin.md) 68 | 69 | ## 形式化验证 70 | 71 | [偏易|Curry-Howard 同构(Curry-Howard Isomorphism)[构造演算]](src/CHIso.md) 72 | 73 | ## 编程范式 74 | 75 | [简单|表驱动编程(Table-Driven Programming)[简单 Rust 基础]](src/TableDriven.md) 76 | 77 | [简单|续延(Continuation)[简单 Rust 基础]](src/Continuation.md) 78 | 79 | [中等|代数作用(Algebraic Effect)[简单 Rust 基础,续延]](src/Algeff.md) 80 | 81 | [中等|依赖注入(Dependency Injection)[Java基础,Monad,代数作用]](doc/DepsInj.md) 82 | 83 | [中等|提升(Lifting)[Rust 基础,HKT,Monad]](src/Lifting.md) 84 | 85 | ## 编译原理 86 | 87 | [较难|解析器单子(Parser Monad)[Java基础,HKT,Monad]](doc/ParserM.md) 88 | 89 | [中等|解析器组合子(Parser Combinator)[Java基础,HKT,Monad]](doc/Parsec.md) -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tango::process_root().unwrap() 3 | } -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /src/ADT.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:代数数据类型 (ADT) 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础 6 | 7 | ```rust 8 | use std::collections::HashMap; 9 | use std::fmt::{Display, Formatter, Result}; 10 | 11 | use self::Bool::*; 12 | use self::List::*; 13 | use self::Nat::*; 14 | ``` 15 | 16 | ## 积类型(Product type) 17 | 18 | 积类型是指同时包括多个值的类型,比如 Rust 中的 struct 就会包括多个字段: 19 | 20 | ```rust 21 | struct Student { 22 | name: String, 23 | id: i64, 24 | } 25 | ``` 26 | 27 | 而上面这段代码中 Student 的类型中既有 String 类型的值也有 isize 类型的值。这种情况我们称其为 String 和 isize 的「积」,即`String * i64`。 28 | 29 | ## 和类型(Sum type) 30 | 31 | 和类型是指可以是某一些类型之一的类型,在 Rust 中可以用 enum 来表示: 32 | 33 | ```rust 34 | enum SchoolPerson { 35 | Student { name: String, id: i64 }, 36 | Teacher { name: String, office: String }, 37 | } 38 | ``` 39 | 40 | SchoolPerson 可能是 Student 也可能是 Teacher。这种类型存在多种“变体”的情形,我们称之为 Student 和 Teacher 的「和」,即`String * isize + String * String`。使用时可以通过 Pattern Matching 知道当前的 StudentPerson 具体是 Student 还是 Teacher。 41 | 42 | ## 代数数据类型(ADT, Algebraic Data Type) 43 | 44 | 由和类型与积类型组合构造出的类型就是代数数据类型,其中代数指的就是和与积的操作。 45 | 46 | ### 布尔类型 47 | 48 | 利用和类型的枚举特性与积类型的组合特性,我们可以构造出 Rust 中本来很基础的基础类型,比如枚举布尔的两个量来构造布尔类型: 49 | 50 | ```rust 51 | enum Bool { 52 | True, 53 | False, 54 | } 55 | ``` 56 | 57 | 模式匹配可以用来判定某个 Bool 类型的值是 True 还是 False。 58 | 59 | ```rust 60 | #[test] 61 | fn test_bool() { 62 | let b = True; 63 | match b { 64 | True => (), 65 | False => panic!("oh my?"), 66 | }; 67 | } 68 | ``` 69 | 70 | ### 自然数 71 | 72 | 让我们看一些更有趣的结构。我们知道,一个自然数要么是 0,要么是另一个自然数 +1。如果理解上有困难,可以将其看作是一种“一进制”的计数方法。这种自然数的构造法被称为皮亚诺结构。利用 ADT,我们可以轻易表达出这种结构: 73 | 74 | ```rust 75 | enum Nat { 76 | S(Box), 77 | O, 78 | } 79 | macro_rules! S { 80 | ($n: expr) => { 81 | S(Box::new($n)) 82 | }; 83 | } 84 | ``` 85 | 86 | 其中,`O` 表示自然数 0,而 `S` 则代表某个自然数的后继(即+1)。例如,3 可以用`S!(S!(S!(O)))`来表示。 87 | 88 | ```rust 89 | impl Display for Nat { 90 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 91 | let n = std::iter::successors(Some(self), |n| match n { 92 | S(n) => Some(&*n), 93 | O => None, 94 | }).skip(1).count(); 95 | write!(f, "{}", n) 96 | } 97 | } 98 | 99 | #[test] 100 | fn test_nat() { 101 | let nat = S!(S!(S!(O))); 102 | assert_eq!(format!("{}", nat), String::from("3")); 103 | } 104 | ``` 105 | 106 | > 读者可能会注意到,我们使用了`Box`而不是`Nat`来表达 `S`,这是由于在 Rust 中,所有栈上的数据结构的空间大小(内存占用)必须在编译期决定。如果我们将 `S` 定义为 `S(Nat)`,则以下两个表达式都是 `Nat` 类型的合法实例:`S(O)`, `S(S(S(O)))`。显然,这两个值在内存中占用的长度不一致。在最糟糕的情况下,由于自然数大小没有上界,其大小甚至可能是无穷的。这说明如果如此定义 `Nat`,我们的编译器将不知道它具体要占用多大的空间。因此,我们使用了`Box`这一智能指针类型,将内嵌的`Nat`放到堆内存中,那么`Nat`在栈上的大小即可确定了。 107 | > 108 | > 为了降低阅读的困难程度,我们使用了宏来简化其表达,以下不再赘述。 109 | 110 | ### 链表 111 | 112 | ```rust 113 | pub enum List { 114 | Nil, 115 | Cons(T, Box>), 116 | } 117 | impl Default for List { 118 | fn default() -> Self { 119 | Nil 120 | } 121 | } 122 | #[macro_export] 123 | macro_rules! Cons { 124 | ($n: expr, $l: expr) => { 125 | List::Cons($n, Box::new($l)) 126 | }; 127 | } 128 | ``` 129 | 130 | `[1, 3, 4]`可以被表示为 `Cons!(1, Cons!(3, Cons!(4, Nil)))` 131 | 132 | ## 何以代数? 133 | 134 | 代数数据类型之所以被称为“代数”,是因为其可以像代数一样进行运算。其实,每种代数数据类型都对应着一个值,即这种数据类型可能的实例数量。 135 | 136 | 显然,积类型的实例数量来自各个字段可能情况的组合,也就是各字段实例数量相乘。而和类型的实例数量,就是各种可能类型的实例数量之和。 137 | 138 | 例如,`Bool`的实例只有`True`和`False`两种情况,其对应的值就是`1+1`。而`Nat`除了最初的`O`以外,对于每个`Nat`值`n`都存在`S(n)`,其也是`Nat`类型的值。那么,我们可以将`Nat`对应到`1+1+1+...`,其中每一个 1 都代表一个自然数。至于 List 的类型就是`1+x(1+x(...))`也就是`1+x^2+x^3...`其中 `x `就是 List 所存类型的实例数量。 139 | 140 | > 容易注意到,上述的`Nat`与`List`类型,它们的合法实例数量都是无穷的。那么在之前定义数据结构时,如果不使用指针则其空间大小不可确定,即是平凡的事情了。 141 | 142 | 到现在为止,我们已经通过代数数据类型粗略定义出了加法与乘法。其实,我们还可以定义出零值以及指数计算。另外,加法的交换率等定理可以通过这套类型系统进行证明。感兴趣的读者可以查询相关资料,进一步进行探究。 143 | 144 | ## 实际运用 145 | 146 | ADT 最适合构造树状的结构,比如解析 JSON 出的结果需要一个聚合数据结构。 147 | 148 | ```rust 149 | enum JsonValue { 150 | Bool(bool), 151 | Int(i64), 152 | String(String), 153 | Array(Vec), 154 | Map(HashMap), 155 | } 156 | ``` 157 | -------------------------------------------------------------------------------- /src/Algeff.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:代数作用 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础,续延 6 | 7 | ```rust 8 | use std::fmt::{self, Display, Formatter}; 9 | use std::error::Error; 10 | ``` 11 | 12 | ## 可恢复异常 13 | 14 | 有时候我们希望在异常抛出后经过保存异常信息再跳回原来的地方继续执行。 15 | 16 | 参考 [延续](Continuation.md) 一节中 `try-catch` 的实现, 17 | 如果我们有了异常抛出时的续延那么可以带有 `resume` 块, 在 `catch` 块中调用这个续延就能恢复之前的执行状态。 18 | 19 | 下面是实现可恢复异常的 `try-catch` : 20 | 21 | ```rust 22 | type BareResumeFuncTy = dyn FnOnce(); 23 | type FinalFuncTy = dyn FnOnce(); 24 | type BareCatchFuncTy = dyn FnOnce(Box, Box); 25 | type BareBodyFuncTy = dyn FnOnce(Box, Box); 26 | 27 | fn r#try(body: Box, catch: Box, r#final: Box) { 28 | body(catch, r#final); 29 | } 30 | ``` 31 | 32 | 然后就可以像下面这样使用: 33 | 34 | ```rust 35 | #[derive(Debug, Copy, Clone)] 36 | struct ZeroDivision; 37 | 38 | impl Display for ZeroDivision { 39 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 40 | write!(f, "divide by zero") 41 | } 42 | } 43 | 44 | impl Error for ZeroDivision {} 45 | 46 | fn try_div_resume(t: u64) { 47 | r#try( 48 | Box::new(move |throw, r#final| { 49 | println!("try"); 50 | if t == 0 { 51 | throw(Box::new(ZeroDivision), Box::new(|| { 52 | println!("resumed"); 53 | r#final(); 54 | })); 55 | } else { 56 | println!("{}", 100 / t); 57 | r#final(); 58 | } 59 | }), 60 | Box::new(|e, cont| { 61 | println!("catch {:#?}", e); 62 | cont(); 63 | }), 64 | Box::new(|| println!("final")), 65 | ); 66 | } 67 | 68 | #[test] 69 | fn test_try_resume() { 70 | try_div_resume(0); 71 | } 72 | ``` 73 | 74 | 而调用 `try_div_resume(0)` 就会得到: 75 | 76 | ``` 77 | try 78 | catch ZeroDivision 79 | resumed 80 | final 81 | ``` 82 | 83 | ## 代数作用 84 | 85 | 如果说在刚刚异常恢复的基础上希望在恢复时修补之前的异常错误就需要把之前的 `resume` 函数加上参数, 86 | 这样修改以后它就成了代数作用(Algebraic Effect)的基础工具: 87 | 88 | ```rust 89 | type ResumeFuncTy = dyn FnOnce(T); 90 | type CatchFuncTy = dyn FnOnce(Box, Box>); 91 | type BodyFuncTy = dyn FnOnce(Box>, Box); 92 | 93 | fn try_alt(body: Box>, catch: Box>, r#final: Box) { 94 | body(catch, r#final); 95 | } 96 | ``` 97 | 98 | 使用方式如下: 99 | 100 | ```rust 101 | fn try_div_resume_alt(t: u64) { 102 | try_alt( 103 | Box::new(move |throw, r#final| { 104 | println!("try"); 105 | if t == 0 { 106 | throw(Box::new(ZeroDivision), Box::new(|v: u64| { 107 | println!("resumed {}", 100 / v); 108 | r#final(); 109 | })); 110 | } else { 111 | println!("{}", 100 / t); 112 | r#final(); 113 | } 114 | }), 115 | Box::new(|e, cont| { 116 | println!("catch {:#?}", e); 117 | cont(1); 118 | }), 119 | Box::new(|| println!("final")), 120 | ); 121 | } 122 | 123 | #[test] 124 | fn test_try_resume_alt() { 125 | try_div_resume_alt(0); 126 | } 127 | ``` 128 | 129 | 而这个东西能实现不只是异常的功能,从某种程度上来说它能跨越函数发生作用(Perform Effect)。 130 | 131 | 比如说现在有个函数要记录日志,但是它并不关心如何记录日志,输出到标准流还是写入到文件或是上传到数据库。 132 | 这时候它就可以调用 133 | 134 | ``` rust 135 | perform(log_it(INFO, "test"), ...); 136 | ``` 137 | 138 | 来发生(Perform)一个记录日志的作用(Effect)然后再回到之前调用的位置继续执行, 139 | 而具体这个作用产生了什么效果就由调用这个函数的人实现的 `try` 中的 `handler` (`catch`) 决定。 140 | 这样发生作用和执行作用(Handle Effect)就解耦了。 141 | 142 | 进一步讲,发生作用和执行作用是可组合的。对于需要发生记录日志的作用, 143 | 可以预先写一个输出到标准流的的执行器(Handler)一个输出到文件的执行器然后在调用函数的时候按需组合。 144 | 这也就是它是代数的(Algebraic)的原因。 145 | 146 | 细心的读者还会发现这个东西还能跨函数传递数据,在需要某个量的时候调用 147 | 148 | ```java 149 | perform(ask("config"), ...); 150 | ``` 151 | 152 | 就可以获得这个量而不用关心这个量是怎么来的,内存中来还是读取文件或者 HTTP 拉取。从而实现获取和使用的解耦。 153 | 154 | 而且这样的操作和状态单子非常非常像,实际上它就是和相比状态单子来说没有修改操作的读取器单子(Reader Monad)同构。 155 | 156 | 也就是说把执行器函数作为读取器单子的状态并在发生作用的时候执行对应函数就可以达到和用续延实现的代数作用相同的效果, 157 | 反过来也同样可以模拟。 -------------------------------------------------------------------------------- /src/CHIso.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:Curry-Howard 同构 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:构造演算 6 | > 7 | > Rust 部分:Rust 基础,命题逻辑 8 | 9 | ## 记忆碎片 10 | 11 | 我初中刚学几何证明的时候想过一个问题,能否用计算机来自动批改证明。那时候我还在用 VB 语言,能想到的办法也就只有字符串匹配替换。比如说下面的证明: 12 | 13 | ``` 14 | 已知: a ∥ b, c ∥ d, a ∦ d 15 | 求证: b ∦ c 16 | ∵ a ∥ b 17 | a ∦ d 18 | ∴ b ∦ d 19 | ∵ c ∥ d 20 | ∴ b ∦ c 21 | ``` 22 | 23 | 可以用下面的语法来表示: 24 | 25 | ``` 26 | known: parallel(a, b), parallel(c, d), !parallel(a, d) 27 | // 已知 ⇒ 结论 28 | { parallel(a, b), !parallel(a, d) } ⇒ { !parallel(b, d) } 29 | { parallel(c, d), !parallel(b, d) } ⇒ { !parallel(b, c) } 30 | ``` 31 | 32 | 然后对每一步证明遍历一遍公理和已知然后进行匹配。这样当然很低效,匹配证据的顺序的时间复杂度是指数级的,如果每次手动提供依据就可以大大提高效率,比如改成下面的表示法: 33 | 34 | ``` 35 | // 公理 36 | Axiom parallelAxiom { parallel ( a, b ), !parallel ( a, c ) } ⇒ !parallel ( b, c ) 37 | Axiom sym { parallel ( a, b ) } ⇒ parallel ( b, a ) 38 | // 证明 39 | parallelogram { p: parallel ( a, b ), 40 | q: parallel ( c, d ), 41 | r: !parallel ( a, d ) } ⇒ !parallel ( b, c ) 42 | = parallelAxiom ( sym ( q ), sym ( parallelAxiom ( p, r ) ) ) 43 | ``` 44 | 45 | 细想的话实际上 `parallelogram` 的定义有点像是个函数类型: `p, q, r` 三个依据就像是函数的三个参数,指代的三个命题就像是参数的类型,而证据 `parallelAxiom, sym` 46 | 的使用就像是函数调用一样,把一系列已知变换成一个结论。而且 `parallelogram` 这个证明同样也可以作为证据被其他证明使用。 47 | 48 | > 注: 49 | > 50 | > 进行暴力匹配找到解的工具中,有一种被称为 SAT Solver 51 | 52 | ## Curry-Howard 同构 53 | 54 | > 命题即类型,证明即程序 55 | 56 | Curry-Howard 同构(Curry-Howard Isomorphism, 有些范畴人倾向叫它 Curry-Howard 57 | Correspondence)指出了程序和证明的相似性:一个命题可以看做一个类型,蕴含可以看做函数类型,全称量词可以看做 `forall` ,否定可以看做没有实例的空类型(Empty Type, 58 | Void),析取可以看做和类型,合取可以看做积类型。实际上我们可以按照以上规则将任意证明转化成一段程序,而对程序进行类型检查就是对证明的检查。证明的过程就是利用现有实例构造出指定类型的实例的过程。 59 | 60 | 利用 Curry-Howard 同构编写的一种类型检查器可以帮助数学家检查证明过程,这样的类型检查器被称为证明辅助器(Proof Assistant)。比较常见的证明辅助器有 Agda, Arend, Coq, Lean, F* 61 | 等。一个语言能用作辅助证明,最基本要拥有依赖类型(Dependent Type),例如对于上面的简单证明 `p` 的类型 `parallel ( a, b )` 也会依赖 `a, b` 。不过构造演算的类型系统足够表述上面的证明: 62 | 63 | ``` 64 | parallelAxiom = Axiom ( 65 | (a: Line) → (b: Line) → (c: Line) → 66 | parallel a b → !parallel a c → !parallel b c ) 67 | sym = Axiom ( 68 | (a: Line) → (b: Line) → 69 | parallel a b → parallel b a ) 70 | 71 | parallelogram = 72 | (a: Line) ⇒ (b: Line) ⇒ (c: Line) ⇒ (d: Line) ⇒ 73 | (p: parallel a b) ⇒ (q: parallel c d) ⇒ (r: !parallel a d) ⇒ 74 | parallelAxiom d b c (sym c d q) (sym b d (parallelAxiom a b d p r)) 75 | ``` 76 | 77 | 其中 `Axiom` 用于表示公理,公理实际上就是一个包含类型信息的不可计算实例: 78 | 79 | ```java 80 | class Axiom implements Expr { 81 | Expr t; 82 | public Expr reduce() { return this; } 83 | public Expr fullReduce() { return this; } 84 | public Expr checkType(Env env) { return t; } 85 | } 86 | ``` 87 | 88 | > 注: 89 | > 90 | > 由于改写顺序,此时还未改写 Rust 的构造演算。 91 | 92 | 构造出公理时就默认它是正确的,因为我们获得了对应类型的实例。把命题当成公理非常方便但是滥用公理容易造成大问题,如果不慎引入了一个错误的公理那么整个证明都变得不正确了。 93 | 94 | > 注: 95 | > 96 | > 个人觉得 `平行` 最好可以定义为一种等价关系,即存在自反对称传递性,然后 `parallelAxiom` 就可以被推导出了。 97 | 98 | ## 补充:使用 Rust 进行逻辑证明 99 | 100 | > 注: 101 | > 102 | > 若读者对一阶命题逻辑有基础理解,对下文将会有更好的理解。 103 | > 104 | > 本节只为了提供 Curry-Howard Correspondence 的一个直观感受。由于本质上 Rust 的类型系统表述力弱,并不能得出此同构。 105 | > 106 | > 并且由于 Rust 的类型系统的完备性存疑(虽然目前存在证明其完备性的尝试),并且标准库内大量存在 unsafe 代码,使用 Rust 进行证明只能作为娱乐项目( 107 | > 108 | > 毕竟,Rust 设计之初就没打算成为证明助手 x x 109 | 110 | 由于 `min_const_generics` 已经被并入 stable rust,我们实质上已经有了最简陋的依值类型 (dependent type),具体来说的话我们有了简单的 pi type。 (它的简陋体现在,只能用 int, 111 | bool 等极少量内置类型来作为被依赖的值) 112 | 113 | 这意味着,我们现在可以在 Rust 的类型系统中表达带全称量词的一阶逻辑。 114 | 115 | 不幸的是,`never` 类型(换句话说,`bottom`)还在 nightly 阶段,我们仍然无法使用 stable rust 来实现。 116 | 117 | 下面,我们来尝试证明上文中提到的关于平行的一系列引理: 118 | 119 | ### 定义命题 120 | 121 | 我们定义 `平行` 命题(类型)和 `伪` 命题(类型): 122 | 123 | ```rust 124 | #[derive(PartialEq, Eq)] 125 | pub enum Line { 126 | A, 127 | B, 128 | C, 129 | D 130 | } 131 | use self::Line::*; 132 | 133 | #[derive(Copy, Clone, Default)] 134 | pub struct Parallel {} 135 | 136 | type False = !; // never type 137 | ``` 138 | 139 | 注意:bottom type (aka. never type, false type) 没有任何构造函数,这意味着我们永远无法证明一个伪命题(构造一个它的表达式) 140 | 141 | ### 引入公理 142 | 143 | 首先,我们引入伪命题的相关公理:爆炸原理 (Principle of explosion)。也就是说,如果可以证明伪命题(可以构造底类型的实例),我们可以证明(构造)一切。 144 | 145 | ```rust 146 | mod Axioms { 147 | use super::{False, Parallel, Line}; 148 | 149 | pub fn bot_elim(contra: False) -> T { 150 | Default::default() 151 | } 152 | ``` 153 | 154 | 在构造主义逻辑中,我们用`命题推出伪命题(P -> False)`来表达`命题为假 (not P)`这一概念。 显然,我们也不能构造出任何命题(类型) P 的证明(表达式),因为如果可以构造出 P 155 | 的证明,那我们也自然可以证明伪命题,那就乱套了。 156 | 157 | 其次,我们知道 `平行` 是一种等价关系,也就是说它具有自反、对称、传递性: 158 | 159 | - 自反性:对于一切直线 A,A ∥ A 160 | - 对称性:对于一切直线 A 和 B,如果 A ∥ B 那么 B ∥ A 161 | - 传递性:对于一切直线 A B 和 C,如果 A ∥ B 并且 B ∥ C 那么 A ∥ C 162 | 163 | 根据 Curry-Howard Correspondence,蕴含 `->` 表示函数,前提命题是参数的类型,结论命题是返回值的类型。 自然地,这一函数接受前提的证明,提供结论的证明。 164 | 165 | > 注: 166 | > 167 | > 另由于 `肯定前件` 以及 `假设引理` 的正确性,我们有柯里同构。柯里同构是指,以下两种函数其实本质是一样的 168 | > 169 | > fn(a: A, b: B) -> C {} 170 | > 171 | > fn(a: A) -> (fn(b: B) -> C) {} 172 | > 173 | > 这两个操作常被分别称为 `柯里化` 与 `逆柯里化` 174 | 175 | 全称量词意味着,对于所有命题(类型)P,其证明(表达式)都可以被接受,则它对应着 `泛型` 概念。 176 | 177 | 现在我们来表达平行的等价公理 178 | 179 | ```rust 180 | // forall a b, a ∥ b -> b ∥ a 181 | pub fn sym( 182 | p: Parallel<{ a }, { b }>, 183 | ) -> Parallel<{ b }, { a }> { 184 | Default::default() 185 | } 186 | 187 | // forall a b c, a ∥ b -> b ∥ c -> a ∥ c 188 | pub fn trans( 189 | p: Parallel<{ a }, { b }>, 190 | q: Parallel<{ b }, { c }>, 191 | ) -> Parallel<{ a }, { c }> { 192 | Default::default() 193 | } 194 | 195 | // forall a, a ∥ a 196 | pub fn refl() -> Parallel<{ a }, { a }> { 197 | Default::default() 198 | } 199 | } 200 | 201 | use Axioms::{bot_elim, refl, sym, trans}; 202 | ``` 203 | 204 | ### 小试牛刀 205 | 206 | 首先,我们给 `命题为假 (not P)` 提供语法糖 207 | 208 | ```rust 209 | macro_rules! not { 210 | ($p: ty) => { 211 | impl FnOnce($p) -> False 212 | } 213 | } 214 | 215 | macro_rules! not_dyn { 216 | ($p: ty) => { 217 | dyn FnOnce($p) -> False 218 | } 219 | } 220 | ``` 221 | 222 | 我们从最简单的东西开始: 223 | 224 | 矛盾可以推出一切 225 | ```rust 226 | // forall P Q, P -> not P -> Q 227 | fn ex_falso(h1: P, contra: not!(P)) -> Q { 228 | bot_elim(contra(h1)) 229 | } 230 | ``` 231 | 232 | 来一些具体的矛盾 233 | ```rust 234 | // forall a b c, a ∥ b -> a ∦ b -> c ∥ d 235 | fn explosion( 236 | h1: Parallel<{ a }, { b }>, 237 | h2: not!(Parallel<{a}, {b}>), 238 | ) -> Parallel<{ c }, { d }> { 239 | ex_falso(h1, h2) 240 | } 241 | ``` 242 | 243 | 接下来,我们证明一些比较有用的事情: 244 | 245 | 显然地,a ∦ b 推出 b ∦ a 246 | ```rust 247 | // forall a b, a ∦ b -> b ∦ a 248 | fn theorem_neg_par_sym( 249 | hyp: not!(Parallel<{ a }, { b }>), 250 | contra: Parallel<{ b }, { a }>, 251 | ) -> False { 252 | hyp(sym(contra)) 253 | } 254 | ``` 255 | 256 | > 注: 257 | > 258 | > 注意到我们这里展开了否定,并使用了逆柯里化。 259 | > 260 | > 原本我们想证明 `not (a ∥ b) -> not (b ∥ a)`,展开来写就是 `(a ∥ b -> False) -> (b ∥ a -> False)`, 261 | > 由于箭头是右结合的,同时逆柯里化,我们得到 `(not (a ∥ b), b ∥ a) -> False` 262 | 263 | ### 完成证明 264 | 265 | 最后,证明最初我们想要的结论: 266 | 267 | ``` 268 | 已知: a ∥ b, c ∥ d, a ∦ d 269 | 求证: b ∦ c 270 | ``` 271 | 272 | 首先我们证明引理:如果 a ∥ b 且 a ∦ c,那么 c ∦ b 273 | 274 | ```rust 275 | // forall a b c, a ∥ b -> a ∦ c -> c ∦ b 276 | fn lemma( 277 | h1: Parallel<{ a }, { b }>, 278 | h2: not!(Parallel<{ a }, { c }>), 279 | contra: Parallel<{ b }, { c }>, 280 | ) -> False { 281 | h2(trans(h1, contra)) 282 | } 283 | ``` 284 | 285 | 然后,我们即可得到结论 286 | 287 | ```rust 288 | // forall a b c d, a ∥ b -> c ∥ d -> a ∦ d -> b ∦ c 289 | fn theorem_complex( 290 | h1: Parallel<{ a }, { b }>, 291 | h2: Parallel<{ c }, { d }>, 292 | h3: not!(Parallel<{ a }, { d }>), 293 | contra: Parallel<{ b }, { c }>, 294 | ) -> False { 295 | lemma(trans(h1, contra), h3, h2) 296 | } 297 | ``` 298 | 299 | 有了这条定理之后,我们可以将其应用在任何数条直线上,只要其满足前件的要求: 300 | 301 | ```rust 302 | #[test] 303 | fn reasoning() { 304 | let goal: Box)> = box |contra| { 305 | theorem_complex( 306 | Parallel::<{A}, {B}> {}, 307 | Parallel::<{C}, {D}> {}, 308 | |_| loop {}, // this fn never returns 309 | contra, 310 | ) 311 | }; 312 | } 313 | ``` 314 | 315 | > 注: 316 | > 317 | > 注意到定理的第三个输入参数我们没有给任何类型标注,这是因为 Rust 的类型推导可以自动推出所需的类型(命题)。 318 | > 319 | > 为了体现出这个函数的返回值(它的类型是 False)不可能构造出来,函数体被填成了一个死循环,意味着它永远不可能被构造出来。 320 | 321 | ## 补充:练习迫害苏格拉底 322 | 323 | 尝试定义类型 `Human` 表示人类(其中可以只有一个实例 `socrates` 苏格拉底),`Mortal` 表示会死,另加一公理 `人是会死的`, 324 | 来表述以下经典三段论: 325 | 326 | ``` 327 | 所有人都会死的。 328 | 苏格拉底是人。 329 | 所以苏格拉底会死。 330 | ``` 331 | 332 | -------------------------------------------------------------------------------- /src/ChurchE.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:丘奇编码 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:λ 演算 6 | 7 | ## Intro 8 | 9 | 众所周知, λ 演算是一个图灵完备的计算模型,它能计算任何图灵机能算的东西。那么很显然它也能表示任何我们平时所用的 C 、 Java 能表示的数据结构。虽然这听起来挺不可思议的,毕竟 λ 演算中本身只有变量、函数定义、函数应用三种结构。 10 | 11 | 信息的编码大概是计算机科学中最为接近魔法的内容,凝结了最强的人类的智慧结晶。同一个量的不同表现形式,同构、抽象与组合都让人感到惊叹不已。 12 | 13 | 为了方便起见,这里引入一个语法糖 let 绑定(let-binding)来**命名**表达式: 14 | 15 | ``` 16 | x = E 17 | ...后续代码 18 | ``` 19 | 20 | 它解糖(Desugar)后等价于: 21 | 22 | ``` 23 | (λ x. ...后续代码) E 24 | ``` 25 | 26 | ## 布尔 27 | 28 | 通常来说丘奇编码(Church Encoding)的布尔表达为: 29 | 30 | ``` 31 | true = λ x. λ y. x 32 | false = λ x. λ y. y 33 | ``` 34 | 35 | 理论上这两个量的定义互相替换后和这种表达也是同构的,不过通常来说大家约定这种表示因为它更符合直觉。 36 | 37 | 实际上定义了布尔以后并不需要定义 if ,布尔量本身就可以接替 if 的作用,只需要将 if 的两个分支应用上去: 38 | 39 | ``` 40 | (boolValue thenTodo elseTodo) 41 | ``` 42 | 43 | 如果`boolValue`是`true`那么求值就会得到`thenTodo`否则会得到`elseTodo`。 44 | 45 | 我们不需要 if ,这很神奇。不过为了语义考虑也可以定义一个没有实际意义的 if : 46 | 47 | ``` 48 | if = λ x. λ a. λ b. (x a b) 49 | ``` 50 | 51 | 这样 `if true a b` 就可以得到 `a` , `if false a b` 就可以得到 `b` 。 52 | 53 | ## 自然数 54 | 55 | 皮亚诺构造(Peano Construct)是目前普遍使用的自然数定义。简单来说, 0 用 Z 表示, n 用 n 个 S 和一个 Z 表示。比如 3 就是 SSSZ 。而皮亚诺构造的加法就相当于把一个数的 Z 换成另一个数,就比如 3+3 56 | 就是 SSS(SSSZ) 。乘法就相当于把一个数的每个 S 换成另一个数的 S 部分,比如 3*3 就是 (SSS)(SSS)(SSS)Z 。 57 | 58 | 而这在 λ 演算中可以表示为: 59 | 60 | ``` 61 | 0 = λ f. λ x. x 62 | 3 = λ f. λ x. f (f (f x)) 63 | ``` 64 | 65 | 这样的表示方法叫丘奇数(Church number),非常类似于皮亚诺构造。实际上,它是和皮亚诺构造同构的。 66 | 67 | 丘奇数的加法和乘法很简单,加法只需要把 x 替换成另一个数就好了,乘法只需要把f替换成另一个数就好了: 68 | 69 | ``` 70 | + = λ m. λ n. (λ f. λ x. m f (n f x)) 71 | * = λ m. λ n. (λ f. λ x. m (n f) x) 72 | ``` 73 | 74 | 而某种程度上来说,一个自然数就是固定次数的循环,以 x 为初始值,把 f 循环执行 n 遍。比如 m*n 就相当于把 m 循环累加加 n 次。 75 | 76 | 我们不需要 for ,这很神奇。 77 | 78 | ## 元组 79 | 80 | 终于到了数据结构部分, λ 表达式保存数据的原理是把参数全部放在一个接受一个提取器的函数里面: 81 | 82 | ``` 83 | pair = λ a. λ b. λ f. f a b 84 | first = λ p. p (λ x. λ y. x) 85 | second = λ p. p (λ x. λ y. y) 86 | ``` 87 | 88 | 这样就可以保证 `first (pair x y)` 始终等于 `x` 而 `second (pair x y)` 始终等于 `y` 。其中 `λ x. λ y. x` 和 `λ x. λ y. y` 就是提取器函数。 89 | 90 | 进一步讲,把元组串起来就可以变成列表,比如: 91 | 92 | ``` 93 | list' = pair a1 (pair a2 (pair a3 ...)) 94 | ``` 95 | 96 | 而如果列表分叉就成了树: 97 | 98 | ``` 99 | tree' = pair (pair a1 a2) (pair a3 a4) 100 | ``` 101 | 102 | 我们用函数构造出了数据结构,这很神奇。 103 | 104 | ## 补充:在 Rust 中构造 105 | 106 | 以下是上述数据结构在 Rust 中的构造尝试。 107 | 108 | 但是由于 Rust 的局限性,丘奇编码是不能完美表达出来的: 109 | 110 | 1. Rust 使用仿射类型系统,考虑到其线性性质(先无视弱化),意味着编码出的数据只能被消耗一次。 111 | 2. 由于同样的理由,无法实现丘奇数的加法。 112 | 3. 上述演示是在无类型 lambda 演算下展示的,则由元组表达列表没有障碍。 但若要以这种方式在有类型的情况下表达元组组成的列表,需要有依值类型的类型系统,而 Rust 并不是如此。 113 | 114 | > 另注:需要小心谨慎分辨 Fn/FnMut/FnOnce 之间的区别 115 | 116 | ### 布尔 117 | 118 | ```rust 119 | type Bool = Box T>) -> Box T>) -> T>>; 120 | type IfTy = Box) -> Box T>) -> Box T>) -> T>>>; 121 | 122 | #[test] 123 | fn test_church_bool() { 124 | let True: Bool = box |x| box move |y| x(); 125 | let False: Bool = box |x| box move |y| y(); 126 | let If: IfTy = box |x| box move |a| box move |b| x(a)(b); 127 | 128 | let lhs = || 0; 129 | let rhs = || 1; 130 | assert_eq!(True(box lhs)(box rhs), 0); 131 | assert_eq!(If(False)(box lhs)(box rhs), 1); 132 | } 133 | ``` 134 | 135 | ### 自然数 136 | 137 | ```rust 138 | type Nat = Box T>) -> Box T>>; 139 | type TimesTy = Box) -> Box) -> Box T>) -> Box T>>>>; 140 | 141 | #[test] 142 | fn test_church_nat() { 143 | let zero: Nat = box |f| box |x| x; 144 | let two: Nat = box |f| box move |x| f(f(x)); 145 | let three: Nat = box |f| box move |x| f(f(f(x))); 146 | let six: Nat = box |f| box move |x| f(f(f(f(f(f(x)))))); 147 | 148 | // This won't tyck: 149 | // let add = box |m: Nat| box move |n: Nat| (box move |f| box move |x| m(f)(n(f)(x))); 150 | let times: TimesTy = box |m: Nat| box move |n: Nat| (box move |f| box move |x| m(n(f))(x)); 151 | 152 | // We can't test structural equality in Rust. 153 | // Not a formal proof of (2 * 3 == 6), but it can still serve as an example. 154 | let f = box |x: usize| x + 1; 155 | let f_ = box |x: usize| x + 1; 156 | assert_eq!(times(two)(three)(f)(0), six(f_)(0)); 157 | } 158 | ``` 159 | 160 | ### 元组 161 | 162 | ```rust 163 | type PairExtractor = Box Box T>>; 164 | type Pair = Box Box Box) -> T>>>; 165 | 166 | #[test] 167 | fn test_church_pair() { 168 | let pair: Pair = box |a| box move |b| box move |f| f(a)(b); 169 | let first = box |p: Box) -> usize>| p(box |x| box move |y| x); 170 | let second = box |p: Box) -> usize>| p(box |x| box move |y| y); 171 | assert_eq!(first(pair(0)(1)), 0); 172 | } 173 | ``` -------------------------------------------------------------------------------- /src/CoData.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:余代数数据类型 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础,ADT 6 | 7 | ```rust 8 | use crate::ADT::List::*; 9 | use self::CoList::*; 10 | 11 | use compile_fail::compile_fail; 12 | use std::fmt::{Display, Result, Formatter}; 13 | ``` 14 | 15 | ## ADT的局限性 16 | 17 | 很显然,ADT可以构造任何树形的数据结构:树的节点内分支用和类型连接,层级间节点用积类型连接。 18 | 19 | 但是同样很显然ADT并不能搞出环形的数据结构或者说是无穷大小的数据结构。比如下面的代码: 20 | 21 | ```rust 22 | #[compile_fail] 23 | fn fail() { 24 | let list: List = Cons!(1, list); 25 | // ^^^^ not found in this scope 26 | } 27 | ``` 28 | 29 | 编译器会表示`list`在当前的 scope 内不存在。 30 | 31 | 为什么会这样呢?ADT 是归纳构造的,也就是说它必须从非递归的基本元素开始组合构造成更大的元素。 32 | 33 | 如果我们去掉这些基本元素那就没法凭空构造大的元素,也就是说如果去掉归纳的第一步那整个归纳过程毫无意义。 34 | 35 | ## 余代数数据类型 36 | 37 | 余代数数据类型(Coalgebraic Data Type)也就是余归纳数据类型(Coinductive Data Type),代表了自顶向下的数据类型构造思路,思考一个类型可以如何被分解从而构造数据类型。 38 | 39 | 这样在分解过程中再次使用自己这个数据类型本身就是一件非常自然的事情了。 40 | 41 | 不过在编程实现过程中使用自己需要加个惰性数据结构包裹,防止积极求值的语言无限递归生成数据。 42 | 43 | 比如一个列表可以被分解为第一项和剩余的列表: 44 | 45 | ```rust 46 | #[derive(Copy, Clone)] 47 | enum CoList { 48 | CoCons(T, fn()->CoList) 49 | } 50 | ``` 51 | 52 | 这里的函数指针`fn()->CoList`可以做到仅在需要`next`的时候才求值。使用的例子如下: 53 | 54 | ```rust 55 | fn flip_flop() -> CoList { 56 | CoCons(1, ||{CoCons(2, ||flip_flop())}) 57 | } 58 | ``` 59 | 60 | 会产出`CoCons(1, CoCons(2, ...))`的无穷结构。这里的`flip_flop`从某种角度来看实际上就是个长度为 2 的环形结构。 61 | 62 | ```rust 63 | impl Display for CoList { 64 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 65 | let mut values: Vec = vec![]; 66 | let mut colist = self.clone(); 67 | for _ in 0..4 { 68 | let CoCons(value, fn_colist) = colist; 69 | values.push(value); 70 | colist = fn_colist(); 71 | } 72 | write!(f, "{} :: ...", values.iter().map(|x|x.to_string()).collect::>().join(" :: ")) 73 | } 74 | } 75 | #[test] 76 | fn test_cdt() { 77 | assert_eq!(flip_flop().to_string(), "1 :: 2 :: 1 :: 2 :: ...") 78 | } 79 | ``` 80 | 81 | 用这样的思路可以构造出无限大的树、带环的图等数据结构。 82 | 83 | 不过以上都是对余代数数据类型的一种模拟,实际上在对其支持良好的语言都会自动进行辅助构造, 84 | 同时还能处理好对无限大(其实是环)的数据结构的无限递归变换(`map`, `fold` ...)的操作。 85 | 在懒求值的语言中,其 type 定义甚至同时满足 inductive 与 coinductive。 86 | -------------------------------------------------------------------------------- /src/Continuation.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:续延 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:简单 Rust 基础 6 | 7 | ```rust 8 | use std::fmt::{self, Formatter, Display}; 9 | use std::error::Error; 10 | ``` 11 | 12 | ## 续延 13 | 14 | 续延(Continuation)是指代表一个程序未来的函数,其参数是一个程序过去计算的结果。 15 | 16 | 比如对于这个程序: 17 | 18 | ```rust 19 | fn _test() { 20 | let mut i: u64 = 1; // 1 21 | i += 1; // 2 22 | println!("{}", i); // 3 23 | } 24 | ``` 25 | 26 | 它第二行以及之后的续延就是: 27 | 28 | ```rust 29 | fn _cont2(mut i: u64) { 30 | i += 1; // 2 31 | println!("{}", i); // 3 32 | } 33 | ``` 34 | 35 | 而第三行之后的续延是: 36 | 37 | ```rust 38 | fn _cont3(i: u64) { 39 | println!("{}", i); // 3 40 | } 41 | ``` 42 | 43 | 实际上可以把这整个程序的每一行改成一个续延然后用函数调用串起来变成和刚才的程序一样的东西: 44 | 45 | ```rust 46 | fn cont1() { 47 | let i: u64 = 1; // 1 48 | cont2(i); 49 | } 50 | 51 | fn cont2(mut i: u64) { 52 | i += 1; // 2 53 | cont3(i); 54 | } 55 | 56 | fn cont3(i: u64) { 57 | println!("{}", i); // 3 58 | } 59 | 60 | fn test() { 61 | cont1(); 62 | } 63 | ``` 64 | 65 | ## 续延传递风格 66 | 67 | 续延传递风格(Continuation-Passing Style, CPS)是指把程序的续延作为函数的参数来获取函数返回值的编程思路。 68 | 69 | 听上去很难理解,把上面的三个 `cont` 函数改成CPS就很好理解了: 70 | 71 | ```rust 72 | fn logic1(f: impl Fn(u64)) { 73 | let i: u64 = 1; 74 | f(i); // return i 75 | } 76 | fn logic2(mut i: u64, f: impl Fn(u64)) { 77 | i += 1; 78 | f(i); 79 | } 80 | fn logic3(i: u64, f: impl Fn(u64)) { 81 | println!("{}", i); 82 | f(i); 83 | } 84 | fn test_cont() { 85 | logic1( // 获取返回值 i 86 | move |i| logic2(i, 87 | move |i| logic3(i, 88 | move |i| {}))); 89 | } 90 | ``` 91 | 92 | 每个 `logic` 函数的最后一个参数 `f` 就是整个程序的续延,而在每个函数的逻辑结束后整个程序的续延也就是未来会被调用。而 `test` 函数把整个程序组装起来。 93 | 94 | 读者可能已经注意到,`test_cont` 函数写法很像 Monad。实际上这个写法就是 Monad 的写法, Monad 的写法就是 CPS。 95 | 96 | 另一个角度来说,这也是回调函数的写法,每个 `logic` 函数完成逻辑后调用了回调函数 `f` 来完成剩下的逻辑。实际上,异步回调思想很大程度上就是 CPS 。 97 | 98 | > 注: 99 | > 100 | > 个人理解所有的 CPS 应该都可以被改写成 Monad,而 Monad 调整一下类型应该也可以改写成 CPS。 101 | 102 | ## 有界续延 103 | 104 | 考虑有另一个函数 `call_t` 调用了 `test` 函数,如: 105 | 106 | ```rust 107 | fn call_t() { 108 | test(); 109 | println!("3"); 110 | } 111 | ``` 112 | 113 | 那么对于 `logic` 函数来说调用的 `f` 这个续延并不包括 `call_t` 中的打印语句,那么实际上 `f` 这个续延并不是整个函数的未来而是 `test` 这个函数局部的未来。 114 | 115 | 这样代表局部程序的未来的函数就叫有界续延(Delimited Continuation)。 116 | 117 | 实际上在大多时候用的比较多的还是有界续延,因为在获取整个程序的续延还是比较困难的,这需要全用 CPS 的写法。 118 | 119 | ## 异常 120 | 121 | 拿到了有界续延我们就能实现一大堆控制流魔法,这里拿异常处理举个例子,通过CPS写法自己实现一个 `try-throw` 。 122 | 123 | 首先最基本的想法是把每次调用 `try` 的 `catch` 函数保存起来,由于 `try` 可层层嵌套所以每次压入栈中,然后 `throw` 的时候将最近的 `catch` 函数取出来调用即可 124 | 125 | > 注: 126 | > 127 | > 这边为了规避全局的 vector 并简化所有权与引用关系,使用了 call stack 来持有 catch handler,效果是一样的。 128 | 129 | ```rust 130 | type FinalFuncTy = dyn FnOnce(); 131 | type CatchFuncTy = dyn FnOnce(Box, Box); 132 | type BodyFuncTy = dyn FnOnce(Box, Box); 133 | 134 | fn r#try(body: Box, catch: Box, r#final: Box) { 135 | body(catch, r#final); 136 | } 137 | ``` 138 | 139 | 这里 `body` 的参数和 `catch` 的最后一个参数都是有界续延。 140 | 141 | 有了 `try-throw` 就可以按照CPS风格调用它们来达到处理异常的目的: 142 | 143 | ```rust 144 | #[derive(Debug, Copy, Clone)] 145 | struct ZeroDivision; 146 | 147 | impl Display for ZeroDivision { 148 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 149 | write!(f, "divide by zero") 150 | } 151 | } 152 | 153 | impl Error for ZeroDivision {} 154 | 155 | fn try_div(t: u64) { 156 | r#try( 157 | Box::new(move |throw, r#final| { 158 | println!("try"); 159 | if t == 0 { 160 | throw(Box::new(ZeroDivision), r#final); 161 | } else { 162 | println!("{}", 100 / t); 163 | r#final(); 164 | } 165 | }), 166 | Box::new(|e, r#final| { 167 | println!("catch {:#?}", e); 168 | r#final(); 169 | }), 170 | Box::new(|| println!("final")), 171 | ); 172 | } 173 | 174 | #[test] 175 | fn test_try() { 176 | try_div(1); 177 | try_div(0); 178 | } 179 | ``` 180 | 181 | 调用 `try_div(0)` 会得到: 182 | 183 | ``` 184 | try 185 | catch ZeroDivision 186 | final 187 | ``` 188 | 189 | 而调用 `try_div(1)` 会得到: 190 | 191 | ``` 192 | try 193 | 100 194 | final 195 | ``` 196 | -------------------------------------------------------------------------------- /src/GADT.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:广义代数数据类型 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础,ADT 6 | 7 | ```rust 8 | use compile_fail::compile_fail; 9 | ``` 10 | 11 | 在ADT中可以构造出如下类型: 12 | 13 | ```rust 14 | enum ExprFail { 15 | IVal(i64), 16 | BVal(bool), 17 | Add(Box, Box), 18 | Eq(Box, Box) 19 | } 20 | ``` 21 | 22 | 但是这样构造有个问题,很显然`BVal`是不能相加的,而这样的构造并不能防止构造出这样的东西:`Add(Box::new(BVal(true)), Box::new(BVal(false)))`。实际上在这种情况下ADT的表达能力是不足的。 23 | 24 | 一个比较显然的解决办法是给`Expr`添加一个类型参数用于标记表达式的类型。 25 | 由于 Rust 的 enum 不支持添加关联类型,我们需要使用 trait 的 associative types 来模拟这一特性。 26 | 27 | ```rust 28 | trait Expr: Copy + Clone { type Backing; } 29 | 30 | #[derive(Copy, Clone)] 31 | struct IVal(i64); 32 | #[derive(Copy, Clone)] 33 | struct BVal(bool); 34 | #[derive(Copy, Clone)] 35 | struct Add, T2: Expr>(T1, T2); 36 | #[derive(Copy, Clone)] 37 | struct Eq, T2: Expr>(T1, T2); 38 | 39 | impl Expr for IVal { type Backing = i64; } 40 | impl Expr for BVal { type Backing = bool; } 41 | impl, T2: Expr> Expr for Add { type Backing = i64; } 42 | impl, T2: Expr> Expr for Eq { type Backing = bool; } 43 | ``` 44 | 45 | 这样就可以避免构造出两个类型为`bool`的表达式相加,能构造出的表达式都是类型安全的。 46 | 47 | ```rust 48 | #[test] 49 | fn test_gadt() { 50 | let I1 = Eq(IVal(10), IVal(10)); 51 | let I2 = Eq(BVal(true), BVal(true)); 52 | let I3 = Eq(I1, I2); 53 | let v1 = Add(IVal(10), IVal(2)); 54 | let v2 = Add(IVal(0), IVal(3)); 55 | let v3 = Add(v1, v2); 56 | let v4 = Add(v3, v2); 57 | } 58 | 59 | #[compile_fail] 60 | fn fail_gadt() { 61 | // I1-I3, v1-v4 omitted 62 | // This will never check 63 | let fail1 = Eq(I3, v4); 64 | // This won't either 65 | let fail2: Add = Add(I1, I2); 66 | } 67 | ``` 68 | 69 | 需要注意到四个 struct (`IVal`, `BVal`, `Add`, `Eq`) 实现的 trait 并不是 `Expr` 而是包含了 associative type 的 `Expr`,这和ADT并不一样。而这即模拟了广义代数数据类型(Generalized Algebraic Data Type, GADT)。 70 | 71 | > 注: 72 | > 73 | > Rust 模拟 GADT 的实现较为繁琐并且不易于理解,建议参考[原版 Java 模拟实现](https://github.com/goldimax/magic-in-ten-mins/blob/main/doc/GADT.md)。 74 | > 75 | > 另可参考 [refl](https://docs.rs/refl/) 使用了 GAT 构造类型相等的证明来实现 GADT。 -------------------------------------------------------------------------------- /src/HKT.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:高阶类型 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础 6 | 7 | ```rust 8 | use compile_fail::compile_fail; 9 | ``` 10 | 11 | > 注意: 12 | > 13 | > 本节使用了 generic associated types 这一不稳定语言特性,需要使用 Nightly Rust 进行编译,并请注意其实现仍然存在问题,可能会造成编译器内部错误或者异常行为,因此请不要将其应用于生产环境。 14 | > 15 | > HKT 在 Stable Rust 中也可以进行模拟,但是其写法不易于理解。感兴趣的读者可以自行阅读 [Method for Emulating Higher-Kinded Types in Rust](https://gist.github.com/edmundsmith/855fcf0cb35dd467c29a9350481f0ecf) 的实现。 16 | 17 | ## 常常碰到的困难 18 | 19 | 写代码的时候常常会碰到语言表达能力不足的问题,比如下面这段用来给`F`容器中的值进行映射的代码: 20 | 21 | ```rust 22 | #[compile_fail] 23 | fn fail_functor() { 24 | trait Functor { 25 | fn map(&self, f: F) -> Self where F: Fn(&A) -> B; 26 | // ^ type argument not allowed 27 | } 28 | } 29 | ``` 30 | 31 | 并不能通过编译。 32 | 33 | ## 高阶类型 34 | 35 | 假设类型的类型是`Type`,比如`int`和`string`类型都是`Type`。 36 | 37 | 而对于`Vec`这样带有一个泛型参数的类型来说,它相当于一个把类型`T`映射到`Vec`的函数,其类型可以表示为`Type -> Type`。 38 | 39 | 同样的对于`Map`来说它有两个泛型参数,类型可以表示为`(Type, Type) -> Type`。 40 | 41 | 像这样把类型映射到类型的非平凡类型就叫高阶类型(HKT, Higher Kinded Type)。 42 | 43 | 虽然 Rust 中存在这样的高阶类型,但是我们并不能用一个泛型参数表示出来,也就不能写出如上`Self`这样的代码了,因为`Self: impl Functor`是个高阶类型。 44 | 45 | > 如果加一层解决不了问题,那就加两层。 46 | 47 | 虽然在 Rust 中不能直接表示出高阶类型,但是我们可以通过加一个中间层来在保留完整信息的情况下强类型地模拟出高阶类型。 48 | 49 | 首先,我们需要一个中间层来储存高阶类型信息: 50 | 51 | ```rust 52 | pub trait HKT { 53 | type Higher; 54 | } 55 | ``` 56 | 57 | 然后我们就可以用 `Higher` 来表示 `F` ,这样操作完 `Higher` 后我们仍然有完整的类型信息来还原 `F` 的类型。 58 | 59 | 这样,上面`Functor`就可以写成: 60 | 61 | ```rust 62 | trait Functor: HKT { 63 | fn map(&self, f: F) -> Self::Higher where F: Fn(&A) -> B; 64 | } 65 | ``` 66 | 67 | 这样就可以编译通过了。而对于想实现`Functor`的类,需要先实现`HKT`这个中间层,这里拿`Vec`举例: 68 | 69 | ```rust 70 | impl HKT for Vec { 71 | type Higher = Vec; 72 | } 73 | ``` 74 | 75 | 这样,实现`Functor`类就是一件简单的事情了: 76 | 77 | ```rust 78 | impl Functor for Vec { 79 | fn map(&self, f: F) -> Self::Higher where F: Fn(&A) -> B { 80 | self.iter().map(f).collect() 81 | } 82 | } 83 | ``` 84 | 85 | ```rust 86 | #[test] 87 | fn test_hkt() { 88 | let test_vec: Vec = vec![1, 2, 4, 3, 6]; 89 | let result = test_vec.map(|num|{num % 2 == 0}); 90 | assert_eq!(result, vec![false, true, true, false, true]); 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /src/Lifting.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:提升 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础,HKT,Monad 6 | 7 | ```rust 8 | use crate::Monad::Monad; 9 | use compile_fail::compile_fail; 10 | ``` 11 | 12 | ## 概念 13 | 14 | 提升(Lifting)指的是把一个通用函数变成容器映射函数的操作。 15 | 16 | 比如把 `fn(A) -> B` 变成 `fn(M) -> M` 就是一种提升操作。而由于被操作的函数有一个参数所以这个操作也叫 `lift1` 。 17 | 18 | 注意被提升的函数可以有不止一个参数,我们也可以把 `fn(A, B) -> C` 提升为 `fn(M, M) -> M` 。这样两个参数的提升可以称为 `lift2` 。 19 | 20 | 同样,被提升的函数可以没有参数,这时候我们可以看成没有这个函数,也就是把 `A` 提升为 `M` 。这样的提升可以称为 `lift0` 。实际上它也和 `Monad` 中的 `pure` 是同构的。 21 | 22 | 也就是说: 23 | 24 | ```rust 25 | #[compile_fail] 26 | fn lift0(f: A) -> M { 27 | unimplemented!() 28 | } 29 | 30 | #[compile_fail] 31 | fn lift1(f: impl FnOnce(A) -> B) -> impl FnOnce(M) -> M { 32 | unimplemented!() 33 | } 34 | 35 | #[compile_fail] 36 | fn lift2(f: impl FnOnce(A, B) -> C) -> impl FnOnce(M, M) -> M { 37 | unimplemented!() 38 | } 39 | ``` 40 | 41 | > 注: 42 | > 43 | > Rust 不支持高阶类型的多态,所以在实际定义时应提供 M 指代的具体类型才能编译。 44 | 45 | ## fmap 46 | 47 | 看到这个函数签名肯定有人会拍案而起:这不就是 fmap 么? 48 | 49 | fmap is a lifting surly. 因为它符合 lifting 的函数签名,但是 lifting 并不一定是 fmap 。只要符合这样的函数签名就可以说是一个 lifting 。 50 | 51 | 比如对于 list 来说 `f -> x -> x.tail().map(f)` 也符合 lifting 的函数签名,但很显然它不是一个 `fmap` 函数。或者说很多改变结构的函数和 `fmap` 组合还是一个 lifting 函数。 52 | 53 | ## 除此之外呢 54 | 55 | 回到上面那个函数签名,里面有个非泛型的参数 `M` ,这个 `M` 可以是个泛型参数,可以是个包装器比如 `Maybe` ,也可以是个线性容器比如 `List` ,可以是个非线性的容器比如 `Set` 56 | ,甚至可以是抽象容器比如 `Function` 。 57 | 58 | 同时提升操作也可能对容器结构做出一些改变,尤其是对于多参函数的提升可能会对函数的参数做出一些组合。比如对于 `List` 来说 `lift2` 既可以是 `zipMap` 也可也是以 `f` 为操作的卷积。 59 | 60 | > 注: 61 | > 62 | > 由于同样的理由,Rust 无法传入容器类型进行构造。 63 | 64 | ## liftM 65 | 66 | 对于 Monad 来说,存在一种通用的提升操作叫 `liftM` ,比如对于 `vec` 来说 `liftM2` 就是: 67 | 68 | ```rust 69 | fn liftM2Vec(f: impl Fn(A, B) -> C + Copy) 70 | -> impl Fn(Vec, Vec) -> Vec { 71 | move |ma: Vec, mb: Vec| { 72 | mdo!(Vec<_>, 73 | a <- ma; 74 | b <- mb.clone(); 75 | pure f(a, b) 76 | ) 77 | // After macro expansion: 78 | // >::fmap(ma, move |a| 79 | // >::fmap(mb.clone(), move |b| 80 | // >::pure((f(a, b))))) 81 | } 82 | } 83 | ``` 84 | 85 | > 注: 86 | > 87 | > 使用了来自于 Monad 一节的 do notation macro。 88 | 89 | > 注2: 90 | > 91 | > ... 这太扭曲了,但是我实在是糊不出更好的实现,类型体操顶不住了 92 | > 93 | > 如果有看起来阳间一点(可以用比 Copy 更弱一点的 trait bound,或者修改 do macro 使得不用写这个 clone) 94 | > 的实现,欢迎 PR 95 | 96 | 而对 `sum` 进行提升以后的函数输入 `[1, 2, 3]` 和 `[2, 3, 4]` 就会得到 `[3, 4, 5, 4, 5, 6, 5, 6, 7]` 。实际上就是对于任意两个元素组合操作。 97 | 98 | ```rust 99 | #[test] 100 | fn test_liftM2Vec() { 101 | let sum = |a: i64, b: i64| a + b; 102 | assert_eq!(liftM2Vec(sum)(vec![1, 2, 3], vec![2, 3, 4]), 103 | vec![3, 4, 5, 4, 5, 6, 5, 6, 7]); 104 | } 105 | ``` 106 | 107 | 再比如 `liftM5` 在 `Haskell` 中的表述为: 108 | 109 | ```haskell 110 | liftM5 f ma mb mc md me = do 111 | a <- ma 112 | b <- mb 113 | c <- mc 114 | d <- md 115 | e <- me 116 | pure (f a b c d e) 117 | ``` 118 | 119 | 也就是 `liftM[n]` 就相当于嵌套 `n` 层 `flatMap` 提取 `Monad` 中的值然后应用给被提升的函数。 120 | 121 | ## 娱乐:Rust 过程宏实现 Lifting 122 | 123 | > 注: 124 | > 125 | > 这非常不魔法,反倒很工程很黑魔法(不过话说回来,在 Rust 里写魔法教程已经用到了无数的黑魔法了) 126 | > 127 | > 并且本段与本节内容几乎无关,只是一点点个人迷思,建议读者凭兴趣选读,懒得糊可以快进直接看实现 128 | 129 | 以下内容选自 CNCF 所属 TiDB Community 在 2020 Q4 的 Mentorship - Enum RFC 的申请 Coding Task。 130 | 131 | Implement auto_vec procedural macro 132 | 133 | This programming task is aimed to help you learn the basics of the TiKV coprocessor framework. 134 | 135 | In this task, you’ll need to implement an `auto_vec` procedural macro, which will automatically generate a vectorized 136 | version of a function. 137 | 138 | For example, here we have an “add” function. 139 | 140 | ``` rust 141 | #[auto_vec] 142 | fn add(a: Option, b: Option) -> Option { 143 | if let Some(a) = a { 144 | if let Some(b) = b { 145 | return a + b; 146 | } 147 | } 148 | return None; 149 | } 150 | ``` 151 | 152 | The `auto_vec` procedural macro will automatically generate something like 153 | 154 | ``` rust 155 | fn add_vec(a: Vec>, b: Vec>) -> Vec> 156 | ``` 157 | 158 | And users may call `add_vec` to apply “add” on all elements of a Vec. 159 | 160 | ``` rust 161 | let a = vec![Some(1), Some(2), Some(3), None]; 162 | let b = vec![Some(3), Some(2), None, Some(0)]; 163 | println!("{:?}", add_vec(a, b)); // [Some(4), Some(4), None, None] 164 | ``` 165 | 166 | You’ll need to create an `auto_vec` procedural macro for functions of any number of arguments. You’ll need to report a 167 | runtime error if the parameters of the vectorized functions are not correct. 168 | (e.g. a and b have different length) 169 | 170 | 注意到,以上题目要求本质上是要求将一个 `A -> B -> ... -> Z` 的函数提升为 `Vec -> Vec -> ... -> Vec`。 只不过其使用的技术手段并不是 lift 函数,而是通过 Rust 171 | 的过程宏 codegen 出一个新的函数。 172 | 173 | 并且请仔细观察题设,其要求的函数体行为与 liftM 存在不同。 174 | 175 | 以下给出需要通过的单测项目 176 | 177 | ``` rust 178 | // Compile error 179 | #[auto_vec] 180 | fn fn_1() -> String; 181 | 182 | // Compile error 183 | #[auto_vec] 184 | fn fn_2(x: usize); 185 | 186 | // trivial test case 187 | #[auto_vec] 188 | fn add(a: Option, b: Option) -> Option { 189 | if let Some(a) = a { 190 | if let Some(b) = b { 191 | return Some(a + b); 192 | } 193 | } 194 | return None; 195 | } 196 | 197 | // handle generics 198 | #[auto_vec] 199 | fn fn_3, Y: Into>( 200 | a: X, 201 | b: Y, 202 | c: String 203 | ) -> usize { 204 | a.into() + b.into() 205 | } 206 | 207 | struct Location { 208 | x: i64, 209 | y: i64, 210 | } 211 | 212 | // handle arguments with auto-unpacking 213 | #[auto_vec] 214 | fn fn_4((Location { x, .. }, Location { y, .. }, Location { x: x2, .. }): (Location, Location, Location)) -> i64 { 215 | x * y * x2 216 | } 217 | 218 | #[test] 219 | fn autovec_test() { 220 | let a = vec![Some(1), Some(2), Some(3), None]; 221 | let b = vec![Some(3), Some(2), None, Some(0)]; 222 | assert_eq!(add_vec(a, b), [Some(4), Some(4), None, None]); 223 | 224 | let a = vec![ 225 | (Location { x: 1, y: 2 }, Location { x: 4, y: 9 }, Location { x: 9, y: 7 }), 226 | (Location { x: 3, y: 2 }, Location { x: 4, y: 1 }, Location { x: 4, y: 7 }), 227 | (Location { x: 2, y: 2 }, Location { x: 4, y: 2 }, Location { x: 5, y: 7 }) 228 | ]; 229 | assert_eq!(a, vec![81, 12, 20]); 230 | } 231 | ``` 232 | 233 | ~~The implementation of this proc macro is left as an exercise to the readers.~~ 234 | 235 | [参考实现](https://github.com/PhotonQuantum/autovec-impl-rs) -------------------------------------------------------------------------------- /src/Monad.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:单子 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础,HKT 6 | 7 | ```rust 8 | use crate::HKT::HKT; 9 | use compile_fail::compile_fail; 10 | ``` 11 | 12 | > 注意: 13 | > 14 | > 本节依赖的 HKT 使用了不稳定语言特性实现,详见 [高阶类型](HKT.md) 一节。 15 | 16 | ## 单子 17 | 18 | 单子(Monad)是指一种有一个类型参数的数据结构,拥有`pure`(也叫`unit`或者`return`)和`fmap`(也叫`bind`或者`>>=`)两种操作: 19 | 20 | ```rust 21 | pub trait Monad<'a, A>: HKT { 22 | fn pure(v: A) -> Self; 23 | fn fmap(self, f: F) -> Self::Higher 24 | where 25 | F: Fn(A) -> Self::Higher; 26 | } 27 | ``` 28 | 29 | 其中`pure`要求返回一个包含参数类型内容的数据结构,`fmap`要求把值经过`f`以后再串起来。 30 | 31 | 举个最经典的例子: 32 | 33 | ## List Monad 34 | 35 | ```rust 36 | impl Monad<'_, A> for Vec { 37 | fn pure(v: A) -> Self { 38 | vec![v] 39 | } 40 | fn fmap(self, f: F) -> Self::Higher 41 | where 42 | F: Fn(A) -> Self::Higher, 43 | { 44 | // let mut new_vec: Self::Higher = vec![]; 45 | // self.into_iter() 46 | // .for_each(|item| f(item).into_iter().for_each(|i| new_vec.push(i))); 47 | // new_vec 48 | self.into_iter().flat_map(f).collect() 49 | } 50 | } 51 | ``` 52 | 53 | 于是我们可以得到如下平凡的结论: 54 | 55 | ```rust 56 | #[test] 57 | fn test_monad_vec() { 58 | assert_eq!(Vec::::pure(3), vec![3]); 59 | assert_eq!( 60 | vec![1, 2, 3].fmap(|v| vec![v + 1, v + 2]), 61 | vec![2, 3, 3, 4, 4, 5] 62 | ) 63 | } 64 | ``` 65 | 66 | ## Option Monad 67 | 68 | Rust 是一个空安全的语言,想表达很多语言中`null`这一概念,我们需要使用 `Option` 类型。对于初学者来说,面对一串可能出现空值的逻辑来说,判空常常是件麻烦事: 69 | 70 | ```rust 71 | fn add_i(ma: Option, mb: Option) -> Option { 72 | // 提示:请不要在实际场景下写出这样的代码 73 | if let None = ma { 74 | return None; 75 | } 76 | if let None = mb { 77 | return None; 78 | } 79 | Some(ma.unwrap() + mb.unwrap()) 80 | } 81 | ``` 82 | 83 | 现在,我们将`Option`扩展成`HKT`: 84 | 85 | ```rust 86 | impl HKT for Option { 87 | type Higher = Option; 88 | } 89 | ``` 90 | 91 | 可以像这样定义`Option Monad`: 92 | 93 | ```rust 94 | impl Monad<'_, A> for Option { 95 | fn pure(v: A) -> Self { 96 | Some(v) 97 | } 98 | fn fmap(self, f: F) -> Self::Higher 99 | where 100 | F: Fn(A) -> Self::Higher, 101 | { 102 | if let None = self { 103 | None 104 | } else { 105 | f(self.unwrap()) 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | 上面`add_i`的代码就可以改成: 112 | 113 | ```rust 114 | fn add_m(ma: Option, mb: Option) -> Option { 115 | type m = Option; 116 | ma.fmap(|a| { 117 | // a <- ma 118 | mb.fmap(|b| { 119 | // b <- mb 120 | m::pure(a + b) // pure (a + b) 121 | }) 122 | }); 123 | 124 | // 也可以写成如下形式 125 | m::fmap(ma, |a| { 126 | // a <- ma 127 | m::fmap(mb, |b| { 128 | // b <- mb 129 | m::pure(a + b) // pure (a + b) 130 | }) 131 | }) 132 | } 133 | ``` 134 | 135 | 这样看上去比连续`if-return`优雅很多。在一些有语法糖的语言(`Haskell`)里面Monad的逻辑甚至可以像上面右边的注释一样简单明了。 136 | 137 | > 注: 138 | > 139 | > 让我们试着在 Rust 里糊一下这个语法糖(do notation) 140 | 141 | ```rust 142 | macro_rules! mdo { 143 | ($monad:ty, pure $e:expr) => (<$monad>::pure($e)); 144 | 145 | ($monad:ty, _ <- $e:expr ; $($rest:tt)*) => (<$monad>::fmap($e, move |_| mdo!($monad, $($rest)*))); 146 | ($monad:ty, _ <- pure $e:expr ; $($rest:tt)*) => (<$monad>::fmap($monad::pure($e), move |_| mdo!($monad, $($rest)*))); 147 | 148 | ($monad:ty, $v:ident <- $e:expr ; $($rest:tt)*) => (<$monad>::fmap($e, move |$v| mdo!($monad, $($rest)*))); 149 | ($monad:ty, $v:ident <- pure $e:expr ; $($rest:tt)*) => (<$monad>::fmap($monad::pure($e), move |$v| mdo!($monad, $($rest)*))); 150 | 151 | ($monad:ty, ($($vs:pat),*) <- $e:expr ; $($rest:tt)*) => (<$monad>::fmap($e, move |($($vs),*)| mdo!($monad, $($rest)*))); 152 | ($monad:ty, ($($vs:pat),*) <- pure $e:expr ; $($rest:tt)*) => (<$monad>::fmap($monad::pure($e), move |($($vs),*)| mdo!($monad, $($rest)*))); 153 | 154 | ($monad:ty, $e:expr ; $($rest:tt)*) => (<$monad>::fmap($e, move |_| mdo!($monad, $($rest)*))); 155 | ($monad:ty, $e:expr) => ($e) 156 | } 157 | 158 | fn add_m_with_do(ma: Option, mb: Option) -> Option { 159 | type m = Option; 160 | mdo!(m, 161 | a <- ma; 162 | b <- mb; 163 | pure (a + b) 164 | ) 165 | } 166 | ``` 167 | 168 | 敏锐的读者在阅读上述`add_i`的实现时,可能已经发现,在 Rust 中这一函数有更自然的内置写法: 169 | 170 | ```rust 171 | fn add_i_alt(ma: Option, mb: Option) -> Option { 172 | ma.and_then(|a| mb.and_then(|b| Some(a + b))) 173 | } 174 | ``` 175 | 176 | 如果你曾阅读过 Option 相关的文档或源码,你可能会看到如下注释: 177 | 178 | ```rust 179 | /// Returns [`None`] if the option is [`None`], otherwise calls `f` with the 180 | /// wrapped value and returns the result. 181 | /// 182 | /// Some languages call this operation flatmap. 183 | #[compile_fail] 184 | pub fn and_then Option>(self, f: F) -> Option {} 185 | ``` 186 | 187 | 让我们抄下来并改写一下 fmap 的签名: 188 | ```rust 189 | #[compile_fail] 190 | fn fmap Self::Higher>(&self, f: F) -> Self::Higher {} 191 | ``` 192 | 193 | 所以,如果你已经写过一段时间的 Rust,你很有可能已经在不知不觉中大量使用了 Option Monad 和 flatmap 了。 194 | -------------------------------------------------------------------------------- /src/Monoid.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:单位半群 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础 6 | 7 | ```rust 8 | use self::Ordering::*; 9 | ``` 10 | 11 | ## 半群(Semigroup) 12 | 13 | 半群是一种代数结构,在集合 `A` 上包含一个将两个 `A` 的元素映射到 `A` 上的运算即 `<> : (A, A) -> A` ,同时该运算满足**结合律**即 `(a <> b) <> c == a <> (b <> c)` ,那么代数结构 `{<>, A}` 就是一个半群。 14 | 15 | 比如在自然数集上的加法或者乘法可以构成一个半群,再比如字符串集上字符串的连接构成一个半群。 16 | 17 | 用 Rust 代码可以表示为: 18 | 19 | ```rust 20 | trait Semigroup: Sized { 21 | fn op(self, other: Self) -> Self; 22 | } 23 | ``` 24 | 25 | ## 单位半群(Monoid) 26 | 27 | 单位半群是一种带单位元的半群,对于集合 `A` 上的半群 `{<>, A}` ,`A`中的元素`a`使`A`中的所有元素`x`满足 `x <> a` 和 `a <> x` 都等于 `x`,则 `a` 就是 `{<>, A}` 上的单位元。 28 | 29 | > 注:单位半群有另一个常用的名字叫“幺半群”,其中幺作数字一之解。 30 | 31 | 举个例子,`{+, 自然数集}`的单位元就是0,`{*, 自然数集}`的单位元就是1,`{+, 字符串集}`的单位元就是空串`""`。 32 | 33 | 用 Rust 代码可以表示为: 34 | 35 | ```rust 36 | trait Monoid: Semigroup { 37 | fn id() -> Self; 38 | } 39 | ``` 40 | 41 | ## 应用:Option 42 | 43 | 在 Rust 中有类型`Option`可以用来表示可能有值的类型,而我们可以将它定义为 Monoid。 44 | 45 | 定义其上的二元操作:取第一个不为空的值,单位元为 `None`: 46 | 47 | ```rust 48 | impl Semigroup for Option { 49 | fn op(self, b: Self) -> Self { 50 | if self.is_some() { self } else { b } 51 | } 52 | } 53 | 54 | impl Monoid for Option { 55 | fn id() -> Self { None } 56 | } 57 | ``` 58 | 59 | 这样对于 ops 来说我们将获得一串 Option 中第一个不为空的值,对于需要进行一连串尝试操作可以这样写: 60 | 61 | ```rust 62 | #[test] 63 | fn test_monoid_option() { 64 | let result = Option::::id().op(None).op(Some(2)).op(Some(3)).op(None); 65 | assert_eq!(result, Some(2)); 66 | } 67 | ``` 68 | 69 | > 注: 70 | > 71 | > 注意到 Rust 标准库中 `Option` 的 `and` 方法与此处 `op` 有相同的行为。 72 | 73 | ## 应用:Ordering 74 | 75 | 可以利用 Monoid 实现带优先级的比较。 76 | 77 | ```rust 78 | #[derive(Debug, PartialEq)] 79 | enum Ordering { 80 | Lt, 81 | Eq, 82 | Gt 83 | } 84 | fn compare_str(a: &str, b: &str) -> Ordering { 85 | if a < b { Lt } else if a > b { Gt } else { Eq } 86 | } 87 | ``` 88 | 89 | 定义一种 `Ordering` 上的二元操作为取第一个不为等的值,单位元为 `Eq`: 90 | 91 | ```rust 92 | impl Semigroup for Ordering { 93 | fn op(self, other: Self) -> Self { 94 | if self == Eq { other } else { self } 95 | } 96 | } 97 | 98 | impl Monoid for Ordering { 99 | fn id() -> Self { Eq } 100 | } 101 | ``` 102 | 103 | 同样如果有一串带有优先级的比较操作就可以用appends串起来,比如: 104 | 105 | ```rust 106 | #[derive(Copy, Clone)] 107 | struct Student<'a> { 108 | name: &'a str, 109 | sex: &'a str, 110 | from: &'a str 111 | } 112 | 113 | impl Student<'_> { 114 | fn compare(&self, other: &Student) -> Ordering { 115 | Ordering::id() 116 | .op(compare_str(self.name, other.name)) 117 | .op(compare_str(self.sex, other.sex)) 118 | .op(compare_str(self.from, other.from)) 119 | } 120 | } 121 | ``` 122 | 123 | 这样的写法在判断条件更为复杂时可能会比一连串 `if-else` 可读性更高。 124 | 125 | ```rust 126 | #[test] 127 | fn test_monoid_ordering() { 128 | let student_1 = Student { name: "Alice", sex: "Female", from: "Utopia" }; 129 | let student_2 = Student { name: "Dorothy", sex: "Female", from: "Utopia" }; 130 | let student_3 = Student { name: "Alice", sex: "Female", from: "Vulcan" }; 131 | assert_eq!(student_1.compare(&student_2), Lt); 132 | assert_eq!(student_3.compare(&student_1), Gt); 133 | assert_eq!(student_1.compare(&student_3), Lt); 134 | assert_eq!(student_1.compare(&student_1), Eq); 135 | } 136 | ``` 137 | 138 | > 注: 139 | > 140 | > 注意到 Rust 标准库中的 `std::cmp::Ordering` 具有与此处定义的 `Ordering` 类型有几乎一样的行为,其 `op` 方法被称为 `then`。 141 | 142 | ## 接口衍生 143 | 144 | 在 Monoid 接口里面加一些辅助方法可以支持更多方便的操作: 145 | 146 | ```rust 147 | trait MonoidExt: Monoid { 148 | fn ops(bs: impl IntoIterator) -> Self { 149 | bs.into_iter().fold(Self::id(), |a, b| a.op(b)) 150 | } 151 | fn cond(condition: bool, then: Self, els: Self) -> Self { 152 | if condition { then } else { els } 153 | } 154 | fn when(condition: bool, then: Self) -> Self { 155 | Self::cond(condition, then, Self::id()) 156 | } 157 | } 158 | 159 | impl MonoidExt for T {} 160 | 161 | type Thunk = Box; 162 | 163 | macro_rules! thunk { 164 | ($($capture: ident)*, $body: block) => {{ 165 | $(let mut $capture = $capture.clone())*; 166 | Box::new(move || $body) as Thunk 167 | }}; 168 | } 169 | 170 | impl Semigroup for Thunk { 171 | fn op(self, other: Self) -> Self { 172 | Box::new(|| { 173 | self(); 174 | other(); 175 | }) 176 | } 177 | } 178 | 179 | impl Monoid for Thunk { 180 | fn id() -> Self { 181 | Box::new(|| {}) 182 | } 183 | } 184 | ``` 185 | 186 | 然后就可以像下面这样使用: 187 | 188 | ```rust 189 | use std::io::Write; 190 | 191 | #[test] 192 | fn test_monoid_ext() { 193 | let exclaim = true; 194 | let out = DummyWriter::default(); 195 | 196 | let f = Thunk::ops([ 197 | thunk!(out, { write!(out, "Hello ").unwrap(); }), 198 | thunk!(out, { write!(out, "world").unwrap(); }), 199 | Thunk::when(exclaim, thunk!(out, { write!(out, "!").unwrap(); })), 200 | ]); 201 | f(); 202 | 203 | assert_eq!(out.into_inner(), b"Hello world!"); 204 | } 205 | ``` 206 | 207 | > 注:上面 Option 的实现并不是 lazy 的,实际运用中加上非空短路能提高效率。 208 | 209 | ```rust 210 | use std::sync::{Arc, Mutex}; 211 | 212 | #[derive(Clone, Default)] 213 | struct DummyWriter { 214 | buf: Arc>> 215 | } 216 | 217 | impl DummyWriter { 218 | fn into_inner(self) -> Vec { 219 | Arc::try_unwrap(self.buf).unwrap().into_inner().unwrap() 220 | } 221 | } 222 | 223 | impl Write for DummyWriter { 224 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 225 | self.buf.lock().unwrap().extend(buf); 226 | Ok(buf.len()) 227 | } 228 | 229 | fn flush(&mut self) -> std::io::Result<()> { 230 | Ok(()) 231 | } 232 | } 233 | ``` -------------------------------------------------------------------------------- /src/StateMonad.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:状态单子 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能:Rust 基础,HKT,Monad 6 | 7 | ```rust 8 | use crate::HKT::HKT; 9 | use crate::Monad::Monad; 10 | ``` 11 | 12 | ## 函数容器 13 | 14 | Rust 中的不少容器都是可以看成是单子的,上节中 `List Monad` 的实现就是 Vector 的一层 wrapper,而 `Option Monad` 我们也在标准库中找到了等价物。 15 | 16 | 不过单子不仅仅可以是实例意义上的容器,也可以是其他抽象意义上的容器,比如函数。 17 | 18 | 对于一个形如 `S -> A` 形式的函数来说,我们可以把它看成包含了一个 `A` 的惰性容器,只有在给出 `S` 的时候才能知道 `A` 的值。对于这样形式的函数我们同样能写出对应的 `fmap` 19 | ,这里就拿状态单子举例子。 20 | 21 | ## 状态单子 22 | 23 | 状态单子(State Monad)是一种可以包含一个“可变”状态的单子,尽管状态随着逻辑流在变化,但是在内存里面实际上都是不变量。 24 | 25 | 其本质就是在每次状态变化的时候将新状态作为代表接下来逻辑的函数的输入。比如对于: 26 | 27 | ```rust 28 | fn mut_next(mut i: u64) { 29 | i = i + 1; 30 | println!("{}", i); 31 | } 32 | ``` 33 | 34 | 可以用状态单子的思路改写成: 35 | 36 | ```rust 37 | fn inmut_next(i: u64) { 38 | (|i| println!("{}", i))(i + 1); 39 | } 40 | ``` 41 | 42 | State 是一个包含 `run_state` 函数的 Monad,它 (`run_state`) 将某个初态映射到 (终值, 末态),即 `S -> (A, S)`, 而通过组合可以使变化的状态在逻辑间传递: 43 | 44 | ```rust 45 | pub struct State<'a, S, A> { 46 | pub run_state: Box (A, S)>, 47 | } 48 | 49 | impl<'a, S, A> HKT for State<'a, S, A> { 50 | type Higher = State<'a, S, T>; 51 | } 52 | 53 | impl<'a, S: 'a, A: 'a + Clone> Monad<'a, A> for State<'a, S, A> { 54 | fn pure(v: A) -> Self { 55 | State { 56 | run_state: Box::new(move |state: S| (v.clone(), state)), 57 | } 58 | } 59 | 60 | fn fmap(self, f: F) -> Self::Higher 61 | where 62 | F: Fn(A) -> Self::Higher, 63 | { 64 | State { 65 | run_state: Box::new(move |state: S| { 66 | let (interm_value, interm_state) = (self.run_state)(state); 67 | (f(interm_value).run_state)(interm_state) 68 | }), 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | `pure` 操作直接返回当前状态和给定的值, `flatMap` 操作只需要把 `ma` 中的 `A` 取出来然后传给 `f` ,并处理好 `state` 。 75 | 76 | > 注 77 | > 78 | > `flatMap` 其实是将两个 State 进行组合,前一个 State 的终值成为了 f 的参数得到一个新的 State, 79 | > 然后向新的 State 输入前一 State 的终态可以得到组合后 State 的终值和终态。 80 | 81 | 仅仅这样的话 `State` 使用起来并不方便,还需要定义一些常用的操作来读取写入状态: 82 | 83 | ```rust 84 | pub fn get<'a, S: Clone>() -> State<'a, S, S> { 85 | State { run_state: Box::new(|state: S| (state.clone(), state)) } 86 | } 87 | 88 | pub fn put<'a, S: 'a + Clone>(state: S) -> State<'a, S, ()> { 89 | State { run_state: Box::new(move |_| ((), state.clone())) } 90 | } 91 | 92 | pub fn modify<'a, S: 'a + Clone>(f: impl Fn(S) -> S + 'a) -> State<'a, S, ()> { 93 | State::fmap( 94 | get(), 95 | move |x| put(f(x)) 96 | ) 97 | } 98 | 99 | impl<'a, S: 'a + Clone, A: 'a> State<'a, S, A> { 100 | pub fn run(self, state: S) -> (A, S) { 101 | (self.run_state)(state) 102 | } 103 | 104 | pub fn eval(self, state: S) -> A { 105 | (self.run_state)(state).0 106 | } 107 | } 108 | ``` 109 | 110 | ## 使用例 111 | 112 | 求斐波那契数列: 113 | 114 | ```rust 115 | fn fib(n: u64) -> State<'static, (u64, u64), u64> { 116 | match n { 117 | 0 => State::fmap( 118 | get(), 119 | |x: (u64, u64)| { State::pure(x.0) } 120 | ), 121 | _ => State::fmap( 122 | modify(|x: (u64, u64)| { (x.1, x.0 + x.1) }), 123 | move |_| fib(n - 1) 124 | ) 125 | } 126 | } 127 | ``` 128 | 129 | `fib` 函数对应的 Haskell 代码是: 130 | 131 | ```haskell 132 | fib :: Int -> State (Int, Int) Int 133 | fib 0 = do 134 | (_, x) <- get 135 | pure x 136 | fib n = do 137 | modify (\(a, b) -> (b, a + b)) 138 | fib (n - 1) 139 | ``` 140 | 141 | ~~看上去简单很多~~ 142 | 143 | > 注: 144 | > 145 | > Rust 版看上去似乎差不多,而且可以通过 macro 改造成 do notation 146 | 147 | ```rust 148 | fn fib_with_do(n: u64) -> State<'static, (u64, u64), u64> { 149 | match n { 150 | 0 => mdo!(State<_, _>, 151 | (x, _) <- get(); 152 | pure x 153 | ), 154 | _ => mdo!(State<_, _>, 155 | modify(|x: (u64, u64)|(x.1, x.0+x.1)); 156 | fib_with_do(n-1) 157 | ), 158 | } 159 | } 160 | ``` 161 | 162 | ## 有什么用 163 | 164 | 求斐波那契数列有着更简单的写法: 165 | 166 | ```rust 167 | fn imp_fib(n: usize) -> u64 { 168 | let mut a: [u64; 3] = [0, 1, 1]; 169 | for i in 0..(n - 1) { 170 | a[(i + 2) % 3] = a[(i + 1) % 3] + a[i % 3]; 171 | } 172 | a[n % 3] 173 | } 174 | ``` 175 | 176 | 两种实现的区别体现在: 177 | 178 | - 使用了可变对象,而 `State Monad` 仅使用了不可变对象,使得函数是纯函数,但又存储了变化的状态。 179 | 180 | - 非递归,如果改写成递归形式需要在 `fib` 上加一个状态参数,`State Monad` 则已经携带。 181 | 182 | - `State Monad` 的实现是 **可组合** 的,即可以将任意两个状态类型相同的 `State Monad` 组合起来。 183 | 184 | > 注: 185 | > 186 | > 当然还有更 naive 的,无需可变对象,无需传递状态,纯函数的写法,但是其时间复杂度不是线性的 187 | 188 | ```rust 189 | fn naive_fib(n: u32) -> u32 { 190 | match n { 191 | 0 => 0, 192 | 1 => 1, 193 | _ => naive_fib(n - 1) + naive_fib(n - 2), 194 | } 195 | } 196 | ``` 197 | 198 | 对应的 Haskell 代码是 199 | 200 | ```haskell 201 | fib 0 = 0 202 | fib 1 = 1 203 | fib n = fib (n-1) + fib (n-2) 204 | ``` 205 | 206 | ```rust 207 | #[test] 208 | fn test_fib() { 209 | assert_eq!(fib(7).eval((0, 1)), 13); 210 | assert_eq!(fib_with_do(7).eval((0, 1)), 13); 211 | assert_eq!(imp_fib(7), 13); 212 | assert_eq!(naive_fib(7), 13); 213 | } 214 | ``` -------------------------------------------------------------------------------- /src/TableDriven.md: -------------------------------------------------------------------------------- 1 | # 十分钟魔法练习:表驱动编程 2 | 3 | ### By 「玩火」 改写 「光量子」 4 | 5 | > 前置技能: 简单 Rust 基础 6 | 7 | ```rust 8 | use std::collections::HashMap; 9 | use std::fmt::{Display, Formatter, Result}; 10 | use std::rc::Rc; 11 | use std::cell::RefCell; 12 | ``` 13 | 14 | ## Intro 15 | 16 | 表驱动编程被称为是普通程序员和高级程序员的分水岭,而它本身并没有那么难,本身是一种比较基础的写法,甚至很多时候不知道的人也能常常重新发明它。 17 | 18 | 而它本身是锻炼抽象思维的良好途径,几乎所有复杂的系统都能利用表驱动法来进行进一步抽象优化,而这也非常考验程序员的水平。 19 | 20 | ## 数据表 21 | 22 | 学编程最开始总会遇到这样的经典习题: 23 | 24 | > 输入成绩,返回等第,90 以上 A ,80 以上 B ,70 以上 C ,60 以上 D ,否则为 E 25 | 26 | 作为一道考察 `if` 语句的习题初学者总是会写出这样的代码: 27 | 28 | ```rust 29 | fn naive_get_level(score: u64) -> char { 30 | if score >= 90 { return 'A'; } 31 | if score >= 80 { return 'B'; } 32 | if score >= 70 { return 'C'; } 33 | if score >= 60 { return 'D'; } 34 | return 'E'; 35 | } 36 | ``` 37 | 38 | 等学了 `match` 语句以后可以将它改成 `match s/10` 的写法。 39 | 40 | 但是这两种写法都有个同样的问题:如果需要不断添加等第个数那最终 `(naive_)get_level` 函数就会变得很长很长,最终变得不可维护。 41 | 42 | 学会循环和数组后回头再看这个程序,会发现这个程序由反复的 `if score >= _ { return _; }` 构成,可以改成循环结构,把对应的数据塞进数组: 43 | 44 | ```rust 45 | fn get_level(score: u64) -> char { 46 | const TABLE: [(u64, char); 5] = [ 47 | (100, 'A'), 48 | (90, 'B'), 49 | (80, 'C'), 50 | (70, 'D'), 51 | (60, 'E') 52 | ]; 53 | TABLE 54 | .iter() 55 | .fold('\0', |current_grade, (max_score, grade)| 56 | if score <= *max_score { *grade } else { current_grade }) 57 | } 58 | ``` 59 | 60 | 这样的好处是只需要在两个数组中添加一个值就能加一组等第而不需要碰 `get_level` 的逻辑代码。 61 | 62 | 而且进一步讲,数组可以被存在外部文件中作为配置文件,与源代码分离,这样不用重新编译就能轻松添加一组等第。 63 | 64 | 这就是表驱动编程最初阶的形式,通过抽取相似的逻辑并把不同的数据放入表中来避免逻辑重复,提高可读性和可维护性。 65 | 66 | 再举个带修改的例子,写一个有特定商品的购物车: 67 | 68 | ```rust 69 | #[derive(Default, Copy, Clone)] 70 | struct Item<'a> { 71 | pub name: &'a str, 72 | pub price: u64, 73 | pub count: u64, 74 | } 75 | 76 | #[derive(Clone)] 77 | struct ShopList<'a> { 78 | items: Vec>, 79 | } 80 | 81 | impl Default for ShopList<'_> { 82 | fn default() -> Self { 83 | ShopList { 84 | items: vec![ 85 | Item { 86 | name: "water", 87 | price: 1, 88 | ..Default::default() 89 | }, 90 | Item { 91 | name: "cola", 92 | price: 2, 93 | ..Default::default() 94 | }, 95 | Item { 96 | name: "choco", 97 | price: 5, 98 | ..Default::default() 99 | }, 100 | ], 101 | } 102 | } 103 | } 104 | 105 | impl Display for ShopList<'_> { 106 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 107 | write!( 108 | f, 109 | "{}", 110 | self.items 111 | .iter() 112 | .map(|item| format!("{} (${}/per): {}", item.name, item.price, item.count)) 113 | .collect::>() 114 | .join("\n") 115 | ) 116 | } 117 | } 118 | 119 | impl ShopList<'_> { 120 | fn buy(&mut self, name: &str) { 121 | for item in &mut self.items { 122 | if item.name == name { 123 | item.count += 1; 124 | } 125 | } 126 | } 127 | } 128 | 129 | #[test] 130 | fn test_shop_list() { 131 | let mut shop_list = ShopList::default(); 132 | shop_list.buy("cola"); 133 | assert_eq!(shop_list.to_string(), 134 | "water ($1/per): 0\ncola ($2/per): 1\nchoco ($5/per): 0"); 135 | } 136 | ``` 137 | 138 | ## 逻辑表 139 | 140 | 初学者在写习题的时候还会碰到另一种没啥规律的东西,比如: 141 | 142 | > 用户输入 0 时购买 water ,输入 1 时购买 cola ,输入 2 时打印购买的情况,输入 3 退出系统。 143 | 144 | 看似没有可以抽取数据的相似逻辑。但是细想一下,真的没有公共逻辑吗?实际上公共的逻辑在于这些都是在同一个用户输入情况下触发的事件,区别就在于不同输入触发的逻辑不一样,那么其实可以就把逻辑制成表: 145 | 146 | ```rust 147 | struct SimpleUI<'a> { 148 | shop_list: RefCell>, 149 | events: Vec>, 150 | } 151 | 152 | impl Default for SimpleUI<'static> { 153 | fn default() -> Self { 154 | SimpleUI { 155 | shop_list: Default::default(), 156 | events: vec![ 157 | Box::new(|_self| _self.shop_list.borrow_mut().buy("water")), 158 | Box::new(|_self| _self.shop_list.borrow_mut().buy("cola")), 159 | Box::new(|_self| println!("{}", _self.shop_list.borrow())), 160 | ], 161 | } 162 | } 163 | } 164 | 165 | impl SimpleUI<'_> { 166 | fn run_event(&self, e: usize) { 167 | self.events[e](self) 168 | } 169 | } 170 | ``` 171 | 172 | 这样如果需要添加一个用户输入指令只需要在 `event` 表中添加对应逻辑和索引, 修改用户的指令对应的逻辑也变得非常方便。 这样用户输入和时间触发两个逻辑就不会串在一起,维护起来更加方便。 173 | 174 | > 注: 175 | > 176 | > 忍不住想吐槽 Rust 的 borrow checker,由于 events 内会产生对 shop_list 的多个可变引用, 177 | > 不可避免地需要使用 RefCell 来绕掉可变引用编译时排他性的限制,从而产生了内部可变(interior mutability)的行为。 178 | > 179 | > 从本人(改写者)角度来说不是很喜欢这个特性,不过这就是见仁见智的了。 180 | 181 | ## 自动机 182 | 183 | 如果再加个逻辑表能修改的跳转状态就构成了自动机(Automaton)。这里举个例子,利用自动机实现了一个复杂的 UI ,在 `menu` 界面可以选择开始玩或者退出,在 `move` 界面可以选择移动或者打印位置或者返回 `menu` 184 | 界面: 185 | 186 | ```rust 187 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] 188 | enum UIState { 189 | Menu, 190 | GamePlay, 191 | } 192 | 193 | impl Default for UIState { 194 | fn default() -> Self { 195 | UIState::Menu 196 | } 197 | } 198 | 199 | type Jumper = dyn Fn(&ComplexUI, char); 200 | type Draw = dyn Fn(&ComplexUI); 201 | 202 | #[derive(Default, Debug, Eq, PartialEq, Copy, Clone)] 203 | struct ComplexUIState { 204 | state: UIState, 205 | coord: (i64, i64), 206 | } 207 | 208 | struct ComplexUI { 209 | ui: RefCell, 210 | jumpers: HashMap>, 211 | draw: HashMap>, 212 | } 213 | 214 | impl ComplexUI { 215 | fn jump_to(&self, state: UIState) { 216 | self.ui.borrow_mut().state = state; 217 | (self.draw[&state])(self); 218 | } 219 | 220 | fn run_event(&self, c: char) { 221 | let state = self.ui.borrow().state; 222 | (self.jumpers[&state])(self, c); 223 | } 224 | } 225 | 226 | impl Default for ComplexUI { 227 | fn default() -> Self { 228 | let menu_jumper = |_self: &ComplexUI, c: char| { 229 | let mut events: HashMap> = HashMap::new(); 230 | events.insert('p', Box::new(|| _self.jump_to(UIState::GamePlay))); 231 | events.insert('q', Box::new(|| eprintln!("exit"))); 232 | 233 | (*events 234 | .get(&c) 235 | .unwrap_or(&(Box::new(|| eprintln!("invalid key")) as Box)))( 236 | ); 237 | }; 238 | 239 | let move_jumper = |_self: &ComplexUI, c: char| { 240 | let mut events: HashMap> = HashMap::new(); 241 | events.insert( 242 | 'w', 243 | Box::new(|| { 244 | _self.ui.borrow_mut().coord.1 += 1; 245 | _self.draw[&_self.ui.borrow().state](_self); 246 | }), 247 | ); 248 | events.insert( 249 | 's', 250 | Box::new(|| { 251 | _self.ui.borrow_mut().coord.1 -= 1; 252 | _self.draw[&_self.ui.borrow().state](_self); 253 | }), 254 | ); 255 | events.insert( 256 | 'd', 257 | Box::new(|| { 258 | _self.ui.borrow_mut().coord.0 += 1; 259 | _self.draw[&_self.ui.borrow().state](_self); 260 | }), 261 | ); 262 | events.insert( 263 | 'a', 264 | Box::new(|| { 265 | _self.ui.borrow_mut().coord.0 -= 1; 266 | _self.draw[&_self.ui.borrow().state](_self); 267 | }), 268 | ); 269 | events.insert('e', Box::new(|| eprintln!("{:?}", _self.ui.borrow().coord))); 270 | events.insert('q', Box::new(|| _self.jump_to(UIState::Menu))); 271 | 272 | (events 273 | .get(&c) 274 | .unwrap_or(&(Box::new(|| eprintln!("invalid key")) as Box)))( 275 | ); 276 | }; 277 | 278 | let mut jumpers: HashMap> = HashMap::new(); 279 | jumpers.insert(UIState::Menu, Box::new(menu_jumper)); 280 | jumpers.insert(UIState::GamePlay, Box::new(move_jumper)); 281 | 282 | let mut draw: HashMap> = HashMap::new(); 283 | draw.insert( 284 | UIState::Menu, 285 | Box::new(|_self: &ComplexUI| { 286 | eprintln!("draw menu"); 287 | }), 288 | ); 289 | draw.insert( 290 | UIState::GamePlay, 291 | Box::new(|_self: &ComplexUI| { 292 | eprintln!("draw move"); 293 | }), 294 | ); 295 | 296 | ComplexUI { 297 | ui: Default::default(), 298 | jumpers, 299 | draw, 300 | } 301 | } 302 | } 303 | 304 | #[test] 305 | fn test_ui() { 306 | let ui = ComplexUI::default(); 307 | ui.run_event('a'); // print: invalid key 308 | ui.run_event('p'); // jump to gameplay state & draw move 309 | ui.run_event('e'); // print: (0, 0) 310 | ui.run_event('w'); // coord changed to (1, 0) & draw move 311 | ui.run_event('e'); // print: (1, 0) 312 | ui.run_event('q'); // jump to menu state & draw menu 313 | ui.run_event('q'); // exit 314 | } 315 | ``` 316 | 317 | > 注 1: 318 | > 319 | > 这边相较原版来说使用了 enum 来表示状态,用 hashmap 代替了数组。 320 | 321 | > 注 2: 322 | > 323 | > 又是 RefCell 和 interior mutability,还有几处 type checker 推断不出类型导致需要手动标注或者 cast ... 324 | > 325 | > 建议参考[其他语言的实现](https://github.com/goldimax/magic-in-ten-mins/blob/main/doc/TableDriven.md), 326 | > 如果有更好的写法欢迎开 Issue 或者 PR -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | non_snake_case, 3 | unused_variables, 4 | dead_code, 5 | unused_imports, 6 | unused_macros, 7 | incomplete_features, 8 | non_camel_case_types, 9 | non_upper_case_globals, 10 | unreachable_code 11 | )] 12 | #![feature(box_syntax, never_type, adt_const_params)] 13 | 14 | #[macro_use] 15 | mod ADT; 16 | mod CoData; 17 | mod GADT; 18 | mod HKT; 19 | // mod HKTMore; 20 | #[macro_use] 21 | mod Monad; 22 | mod Algeff; 23 | mod CHIso; 24 | mod ChurchE; 25 | mod Continuation; 26 | mod Lifting; 27 | mod Monoid; 28 | mod StateMonad; 29 | mod TableDriven; 30 | --------------------------------------------------------------------------------