├── .github └── workflows │ ├── asciibook.yml │ ├── deploy.yml │ └── tests.yml ├── .gitignore ├── .mdlrc ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.adoc ├── README.md ├── Rakefile ├── SUMMARY.md ├── appendix ├── answers.md ├── glossary.md └── install-linux.md ├── book.adoc ├── chapter01 ├── README.adoc ├── calculator.md ├── conditions.adoc ├── consts.adoc ├── editor.adoc ├── env.adoc ├── functions.adoc ├── rvm.adoc ├── sugar.adoc └── variables.adoc ├── chapter02 └── math.adoc ├── chapter09 └── is_eval_evil.md ├── cjk-gothic.rb ├── data ├── fonts │ └── .gitkeep └── themes │ └── cn-theme.yml ├── images ├── applebasic-code.png ├── applebasic-result.png ├── cake-recipe.jpg ├── font-preview.png ├── punch-card.png ├── stem-415290769594460e2e485922904f345d.svg ├── stem-44336aba7a4c4db9a219816fe9baa42b.svg ├── stem-8170edd3c0342bbe5d6e254fda14cd67.svg ├── stem-8fa14cdd754f91cc6554c9e71929cce7.svg ├── stem-9dd4e461268c8034f5c8564e155c67a6.svg ├── stem-bbd87e394c9a2e7a45fd63ed40ba0722.svg ├── stem-fd91c508f91c2c84498680bd337c1d7a.svg ├── type-system-example.png ├── vscode.png └── xcode-autocomplete.png ├── preface └── README.adoc └── tests └── chapter01.rb /.github/workflows/asciibook.yml: -------------------------------------------------------------------------------- 1 | name: Asciibook 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-18.04 8 | container: 9 | image: asciibook/asciibook:0.0.2-cjk-sc 10 | steps: 11 | - uses: actions/checkout@v2 12 | - run: | 13 | asciibook build book.adoc --dest-dir build/asciibook 14 | - uses: actions/upload-artifact@v1 15 | with: 16 | name: asciibook-build 17 | path: build/asciibook 18 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-deploy: 10 | runs-on: ubuntu-18.04 11 | strategy: 12 | matrix: 13 | node-version: [8] 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | # with: 18 | # submodules: true 19 | - name: Setup ruby 20 | uses: eregon/use-ruby-action@master 21 | with: 22 | ruby-version: 2.7 23 | - name: Install Dependencies 24 | run: | 25 | sudo apt-get -qq -y install p7zip-full 26 | sudo apt-get -qq -y install bison flex libffi-dev libxml2-dev libgdk-pixbuf2.0-dev libcairo2-dev libpango1.0-dev fonts-lyx cmake 27 | gem install bundler 28 | bundle install --jobs 4 --retry 3 29 | - name: Download Fonts 30 | run: | 31 | wget https://github.com/be5invis/Sarasa-Gothic/releases/download/v0.10.2/sarasa-gothic-ttf-0.10.2.7z 32 | 7z x sarasa-gothic-ttf-0.10.2.7z -o./data/fonts/ 33 | - name: Build 34 | run: | 35 | bundle exec rake build 36 | - name: Setup Static Folder 37 | run: | 38 | echo "restartruby.com" > ./build/book/book/CNAME 39 | mkdir ./build/book/book/downloads 40 | cp ./build/book/book.pdf ./build/book/book/downloads/ 41 | cp ./build/book/book.epub ./build/book/book/downloads/ 42 | - name: Deploy 43 | uses: peaceiris/actions-gh-pages@v2 44 | env: 45 | ACTIONS_DEPLOY_KEY: ${{ secrets.GH_PAGES }} 46 | PUBLISH_BRANCH: gh-pages 47 | PUBLISH_DIR: ./build/book/book 48 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ ubuntu-latest, macos-latest, windows-latest ] 10 | ruby: [ 2.7 ] 11 | runs-on: ${{ matrix.os }} 12 | name: ${{ matrix.os }} ${{ matrix.ruby }} CI 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Setup ruby 16 | uses: eregon/use-ruby-action@master 17 | with: 18 | ruby-version: ${{ matrix.ruby }} 19 | - name: Install Dependencies 20 | run: | 21 | gem install bundler 22 | bundle config set without 'build' 23 | bundle install --jobs 4 --retry 3 24 | - name: Run Tests 25 | run: | 26 | bundle exec rake test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /book 3 | /data/fonts/* 4 | /chapter*/images/* 5 | !.gitkeep 6 | 7 | .ruby-version 8 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | all 2 | # Chinese characters have to be in one line. 3 | exclude_rule 'MD013' 4 | # Punctuation in header is allowed 5 | exclude_rule 'MD026' 6 | # Emphasis usage is allowed 7 | exclude_rule 'MD036' 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake', '~> 13.0.0' 4 | gem 'minitest', '~> 5.14.0' 5 | 6 | group :build do 7 | gem 'asciidoctor', '~> 1.5.0' 8 | gem 'coderay', '~> 1.1.0' 9 | gem 'asciidoctor-mathematical', '~> 0.2.2' 10 | gem 'asciidoctor-pdf', '~> 1.5.0.rc.3' 11 | gem 'asciidoctor-epub3', '~> 1.5.0.alpha.13' 12 | end 13 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | Ascii85 (1.0.3) 5 | addressable (2.7.0) 6 | public_suffix (>= 2.0.2, < 5.0) 7 | afm (0.2.2) 8 | asciidoctor (1.5.8) 9 | asciidoctor-epub3 (1.5.0.alpha.13) 10 | asciidoctor (>= 1.5.3, < 3.0.0) 11 | gepub (~> 1.0.0) 12 | asciidoctor-mathematical (0.2.2) 13 | asciidoctor (~> 1.5, >= 1.5.0) 14 | mathematical (~> 1.5, >= 1.5.8) 15 | ruby-enum (~> 0.4) 16 | asciidoctor-pdf (1.5.0.rc.3) 17 | asciidoctor (>= 1.5.3, < 3.0.0) 18 | concurrent-ruby (~> 1.1.0) 19 | prawn (~> 2.2.0) 20 | prawn-icon (~> 2.5.0) 21 | prawn-svg (~> 0.30.0) 22 | prawn-table (~> 0.2.0) 23 | prawn-templates (~> 0.1.0) 24 | safe_yaml (~> 1.0.0) 25 | thread_safe (~> 0.3.0) 26 | treetop (~> 1.6.0) 27 | ttfunk (~> 1.5.0, >= 1.5.1) 28 | coderay (1.1.2) 29 | concurrent-ruby (1.1.5) 30 | css_parser (1.7.1) 31 | addressable 32 | gepub (1.0.7) 33 | nokogiri (>= 1.8.2, < 1.11) 34 | rubyzip (> 1.1.1, < 2.1) 35 | hashery (2.1.2) 36 | i18n (1.8.2) 37 | concurrent-ruby (~> 1.0) 38 | mathematical (1.6.13) 39 | ruby-enum (~> 0.4) 40 | mini_portile2 (2.4.0) 41 | minitest (5.14.0) 42 | nokogiri (1.10.8) 43 | mini_portile2 (~> 2.4.0) 44 | pdf-core (0.7.0) 45 | pdf-reader (2.4.0) 46 | Ascii85 (~> 1.0.0) 47 | afm (~> 0.2.1) 48 | hashery (~> 2.0) 49 | ruby-rc4 50 | ttfunk 51 | polyglot (0.3.5) 52 | prawn (2.2.2) 53 | pdf-core (~> 0.7.0) 54 | ttfunk (~> 1.5) 55 | prawn-icon (2.5.0) 56 | prawn (>= 1.1.0, < 3.0.0) 57 | prawn-svg (0.30.0) 58 | css_parser (~> 1.6) 59 | prawn (>= 0.11.1, < 3) 60 | prawn-table (0.2.2) 61 | prawn (>= 1.3.0, < 3.0.0) 62 | prawn-templates (0.1.2) 63 | pdf-reader (~> 2.0) 64 | prawn (~> 2.2) 65 | public_suffix (4.0.3) 66 | rake (13.0.1) 67 | ruby-enum (0.7.2) 68 | i18n 69 | ruby-rc4 (0.1.5) 70 | rubyzip (2.0.0) 71 | safe_yaml (1.0.5) 72 | thread_safe (0.3.6) 73 | treetop (1.6.10) 74 | polyglot (~> 0.3) 75 | ttfunk (1.5.1) 76 | 77 | PLATFORMS 78 | ruby 79 | 80 | DEPENDENCIES 81 | asciidoctor (~> 1.5.0) 82 | asciidoctor-epub3 (~> 1.5.0.alpha.13) 83 | asciidoctor-mathematical (~> 0.2.2) 84 | asciidoctor-pdf (~> 1.5.0.rc.3) 85 | coderay (~> 1.1.0) 86 | minitest (~> 5.14.0) 87 | rake (~> 13.0.0) 88 | 89 | BUNDLED WITH 90 | 2.1.2 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | == 简介 2 | 3 | 一本关于重新思考编程入门的教程。 4 | 5 | === 如何编译这本书? 6 | 7 | 这本书使用 Asciidoc 标准编写。编译这本书需要有 Ruby 的环境。 8 | 9 | 使用 `bundle install` 安装工具,并使用 `bundle exec rake build` 进行编译。 10 | 11 | 编译完成的电子书在 `./build` 目录下。 12 | 13 | === 如何给这本书提供意见/修改? 14 | 15 | 本书使用 Git 进行维护,并使用 GitHub 对 Git 仓库进行管理。请使用 GitHub 的 Issue 和 Pull Request 系统为本书提供意见和修改。 16 | 17 | === 在线阅读 18 | 19 | https://restartruby.com[restartruby.com] 20 | 21 | === 下载 22 | 23 | - https://restartruby.com/downloads/book.pdf[PDF 版本] 24 | - https://restartruby.com/downloads/book.mobi[EPUB 版本] 25 | 26 | === 鸣谢 27 | 28 | 这本书的写作过程和发行过程离不开下面这些人的帮助,谢谢他们。 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby 再入门 2 | 3 | ## 简介 4 | 5 | 一本关于重新思考编程入门的教程。 6 | 7 | ## Build Status 8 | 9 | [![CI Tests](https://github.com/dsh0416/ruby-relearning/workflows/tests/badge.svg)](https://github.com/dsh0416/ruby-relearning/actions?query=workflow%3Atests) 10 | [![Deployment](https://github.com/dsh0416/ruby-relearning/workflows/deploy/badge.svg)](https://github.com/dsh0416/ruby-relearning/actions?query=workflow%3Adeploy) 11 | 12 | ## 如何编译这本书? 13 | 14 | 这本书使用 Asciidoc 标准编写。编译这本书需要有 Ruby 的环境。 15 | 16 | 使用 `bundle install` 安装工具,并使用 `bundle exec rake build` 进行编译。 17 | 18 | 编译完成的电子书在 `./build` 目录下。 19 | 20 | ## 如何给这本书提供意见/修改? 21 | 22 | 本书使用 Git 进行维护,并使用 GitHub 对 Git 仓库进行管理。请使用 GitHub 的 Issue 和 Pull Request 系统为本书提供意见和修改。 23 | 24 | ## 在线阅读 25 | 26 | [restartruby.com](https://restartruby.com) 27 | 28 | ## 下载 29 | 30 | - [PDF 版本](https://restartruby.com/downloads/book.pdf) 31 | - [EPUB 版本](https://restartruby.com/downloads/book.mobi) 32 | 33 | ## 鸣谢 34 | 35 | 这本书的写作过程和发行过程离不开下面这些人的帮助,谢谢他们。 36 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | task :default => :test 4 | 5 | Rake::TestTask.new(:test) do |t| 6 | t.libs << "tests" 7 | t.test_files = FileList["tests/*.rb"] 8 | end 9 | 10 | task :build => [:clean] do 11 | require 'asciidoctor' 12 | source = 'book.adoc' 13 | doc = Asciidoctor.load_file source 14 | docname = doc.attributes['docname'] 15 | version = doc.attributes['revnumber'] 16 | filename = docname 17 | build_dir = "build/#{docname}" 18 | 19 | # Build html 20 | sh "bundle exec asciidoctor -D #{build_dir}/#{filename} -r asciidoctor-mathematical -a mathematical-format=svg -o index.html #{source}" 21 | cp_r 'images', "#{build_dir}/#{filename}" 22 | sh 'mkdir -p book' 23 | cp_r 'images', "book/images" # FIXME: Hack for existingasciidoctor-mathematical bug 24 | 25 | # Build pdf 26 | sh "bundle exec asciidoctor-pdf -D #{build_dir} -o #{filename}.pdf -r ./cjk-gothic.rb -r asciidoctor-mathematical -a mathematical-format=svg -a pdf-style=cn #{source}" 27 | 28 | # Build epub 29 | sh "bundle exec asciidoctor-epub3 -D #{build_dir} -o #{filename}.epub -r asciidoctor-mathematical -a mathematical-format=svg #{source}" 30 | end 31 | 32 | task :clean do 33 | rm_r Dir['build/*'] 34 | rm_r Dir['book/*'] 35 | end 36 | 37 | task :lint do 38 | file_list = %w(README.md SUMMARY.md chapter*/*.md appendix/*.md preface/*.md) 39 | sh "mdl -s ./.mdlrc #{file_list.join(' ')}" 40 | end 41 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * 简介 4 | * 写在一切开始之前 5 | * 第一周:Hello World 再入门 6 | * ○ 环境搭建 7 | * ○ 如何管理 Ruby 版本 8 | * ○ REPL 和编辑器 9 | * ○ 变量与基本算数 10 | * ◎ 如何理解 Ruby 变量与常量 11 | * ○ 函数基础 12 | * ○ 条件与循环 13 | * ◎ 语法糖 14 | * ○ 练习:计算器 15 | * 第二周:数学运算再入门 16 | * ○ 基础数学运算 17 | * ◎ Ruby 基础数值类型 18 | * ○ 数组与哈希 19 | * ○ 理解引用拷贝 20 | * ◎ 垃圾回收 21 | * ○ 矩阵运算 22 | * ◎ 算法复杂度 23 | * ○ 练习:生命游戏 24 | * ◎ 练习:线性方程组矩阵解法 25 | * 第三周:字符处理再入门 26 | * ○ 常用字符串处理法 27 | * ○ 正则表达式 28 | * ◎ 与 Perl 字符串处理的比较 29 | * ○ 输入处理 30 | * ○ 格式化输出 31 | * ○ 练习:再写一个计算器 32 | * ◎ 练习:简易 CLI 33 | * 第四周:面向对象再入门 34 | * ○ 类与实例 35 | * ○ 继承与重写 36 | * ○ 万物皆是对象 37 | * ◎ 鸭子类型 38 | * ○ 危险方法和布尔方法 39 | * ○ 模组与命名空间 40 | * ◎ 和 Java 对象系统比较 41 | * ◎ 面向对象是必需吗? 42 | * ○ 练习:迷你卡牌游戏 43 | * ◎ 练习:迷你卡牌游戏 AI 44 | * 第五周:Lambda 再入门 45 | * ○ 方法块 46 | * ○ 过程 与 Lambda 47 | * ◎ 作用域与 binding 48 | * ◎ Lambda 演算 49 | * ○ 与面向对象结合 50 | * ○ 异常处理 51 | * ◎ 深入理解异常处理 52 | * ◎ 纤程 Fiber 53 | * ○ 练习:自定义批处理 54 | * ◎ 练习:实现 Y 组合子 55 | * 第六周:Ruby 工程化入门 56 | * ○ 注释 57 | * ○ require 58 | * ◎ require 的内部实现 59 | * ○ RubyGems 60 | * ○ Bundler 61 | * ◎ rdoc 文档 62 | * ○ 练习:Sinatra 小网站 63 | * ○ 打开 RubyGems 64 | * ○ 练习:做一个简单的 RubyGems 65 | * ◎ 练习:自制简单 Ruby on Rails 66 | * 第七周:系统调用再入门 67 | * ○ I/O 网络栈 68 | * ○ *NIX 进程 69 | * ◎ FFI 70 | * ○ 练习:朴素 Shell 71 | * ◎ 练习:在 GitHub 上画图 72 | * 第八周:并行编程再入门 73 | * ○ 多线程开发 74 | * ◎ Ruby GIL 75 | * ○ 自旋锁 76 | * ◎ 哲学家就餐问题 77 | * ○ 多进程开发 78 | * ○ 练习:文件批处理 79 | * ◎ 练习:高速爬虫 80 | * 第九周:Ruby 元编程入门 81 | * ○ 方法扩展 82 | * ○ 动态方法 83 | * ◎ method_mssing 84 | * ○ Is eval evil? 85 | * ○ 练习:猴子补丁 86 | * ◎ 练习:写一个朴素的 DSL 87 | * ◎ 练习:做一个 C 扩展 RubyGems 88 | * 第十周:加入 Ruby 社区入门 89 | * ○ 如何继续学习? 90 | * ○ 关注 Ruby 更新 91 | * ○ Ruby 会议 92 | * ◎ 关注 Ruby 开发进度 93 | * ◎ 为 Ruby 提交 Patch 94 | * 后记 95 | * 附录一:新手如何安装 Linux 开发版? 96 | * 附录二:术语表 97 | * 附录三:参考答案 98 | -------------------------------------------------------------------------------- /appendix/answers.md: -------------------------------------------------------------------------------- 1 | # 参考答案 2 | 3 | 参考答案不唯一,跑得起来就是好答案,跑不起来就是错答案。 4 | 5 | ## 局部变量 6 | 7 | 创建两个变量 `a` 和 `b`,给它们赋两个整数,计算并打印它们的和。 8 | 9 | ```ruby 10 | a = 1 11 | b = 1 12 | puts a + b 13 | ``` 14 | 15 | 创建两个变量 `a` 和 `b`,给它们赋两个整数,计算并打印它们的积。 16 | 17 | ```ruby 18 | a = 1 19 | b = 1 20 | puts a * b 21 | ``` 22 | 23 | 创建三个变量 `a` 和 `b` 和 `c`,给 `a` 和 `b` 赋两个整数,计算它们的和,将它们的结果写入 `c` 变量中。 24 | 25 | ```ruby 26 | a = 1 27 | b = 1 28 | c = a + b 29 | ``` 30 | -------------------------------------------------------------------------------- /appendix/glossary.md: -------------------------------------------------------------------------------- 1 | # 术语表 2 | 3 | 本术语表收录本书中所有涉及术语的中英文对照与出现位置。本文所使用的简体中文术语,是中国大陆地区通行的用法,可能和其它地区的简体字例如大马简体或新加坡简体存在一定差异。 4 | 5 | 在中文互联网查询相关信息时,常常还会阅读到对应的繁体中文内容,而台湾、香港、澳门所使用的术语与大陆的存在一定差异,其中香港与澳门的繁体中文较为通用。本术语表一并收录以方便各位速查。 6 | 7 | 本术语表按汉语拼音字母序排序。 8 | 9 | | 中国大陆简体 | 臺灣繁體 | 香港、澳门繁體 | 英文 | 出现位置 | 10 | | ------------ | -------- | -------- | ---- | -------- | 11 | | 编译器 | 編譯器 | 編譯器 | compiler | [环境搭建](/chapter01/env.md) | 12 | | 解释器 | 直譯器 | 直譯器 | interpreter | [环境搭建](/chapter01/env.md) | 13 | -------------------------------------------------------------------------------- /appendix/install-linux.md: -------------------------------------------------------------------------------- 1 | # 新手如何安装 Linux 开发版? 2 | -------------------------------------------------------------------------------- /book.adoc: -------------------------------------------------------------------------------- 1 | = Ruby 再入门 2 | Delton Ding 3 | v0.1.0, 2019-02-08 4 | :doctype: book 5 | :toc: left 6 | :toc-title: 目录 7 | :source-highlighter: coderay 8 | :latex: 9 | :imagesdir: images 10 | :icons: font 11 | // :front-cover-image: image:cover.png[Front Cover,1050,1600] 12 | 13 | include::README.adoc[] 14 | 15 | include::preface/README.adoc[] 16 | 17 | include::chapter01/README.adoc[] 18 | 19 | include::chapter01/env.adoc[] 20 | 21 | include::chapter01/rvm.adoc[] 22 | 23 | include::chapter01/editor.adoc[] 24 | 25 | include::chapter01/variables.adoc[] 26 | 27 | include::chapter01/consts.adoc[] 28 | 29 | include::chapter01/functions.adoc[] 30 | 31 | include::chapter01/conditions.adoc[] 32 | 33 | include::chapter01/sugar.adoc[] 34 | 35 | == 第二周:数学运算再入门 36 | 37 | include::chapter02/math.adoc[] 38 | -------------------------------------------------------------------------------- /chapter01/README.adoc: -------------------------------------------------------------------------------- 1 | == 第一周:Hello World 再入门 2 | 3 | === 简介 4 | 5 | Ruby 的 Hello World 怎么写? 6 | 7 | [source,ruby] 8 | ---- 9 | puts 'Hello World' 10 | ---- 11 | 12 | 学会了,今天也是努力的一天,睡觉吧。 13 | 14 | **等一等!睡太早啦!** 15 | 16 | ==== 什么是 Hello World? 17 | 18 | Hello World 就是在屏幕上显示一行 Hello World 的最简单的程序。这通常是程序员接触一门新语言时所需要写的第一个程序。 19 | 20 | ==== 为什么要学习 Hello World? 21 | 22 | Hello World 程序的意义绝对不单单是打印一行「Hello World」这么简单。其核心是用来确认程序开发环境和运行环境是不是安装妥当,同时也能对语言的开发进行一个初步的认识。砍柴不误磨刀工,那我们花一些时间,仔细探讨 Ruby 的开发、运行环境配置。 23 | 24 | 让我们一起开心地开始吧。 25 | -------------------------------------------------------------------------------- /chapter01/calculator.md: -------------------------------------------------------------------------------- 1 | # 练习:计算器 2 | -------------------------------------------------------------------------------- /chapter01/conditions.adoc: -------------------------------------------------------------------------------- 1 | === 条件与循环 2 | 3 | ==== 条件判断 4 | 5 | `if` 6 | `case...when...` 7 | 8 | ==== 循环 9 | 10 | `while` 11 | 12 | ===== 范围 13 | 14 | `for i in (N..M)` 15 | 16 | `(N..M).each` 17 | 18 | ===== 迭代器 19 | 20 | `.each` 21 | 22 | `N.times do` 23 | -------------------------------------------------------------------------------- /chapter01/consts.adoc: -------------------------------------------------------------------------------- 1 | === 如何理解 Ruby 变量与常量 2 | 3 | CAUTION: 本章节涉及较为进阶内容,建议第一次接触编程或缺乏编程经验的开发者暂时跳过这一章节。 4 | 5 | **让人出乎意料的是:Ruby 并没有真正意义的常量** 6 | 7 | 这句话一出口,必然有人不服。「Ruby 里全大写的关键字,不就是常量吗?」此言差矣。虽然 Ruby 中的全大写的关键字会被作为常量来识别,但是 Ruby 中的常量本质上还是变量,因为你依然可以给常量任意赋值,只是会触发警告。 8 | 9 | [source,ruby] 10 | ---- 11 | CONST_A = 'foo' 12 | CONST_A = 'bar' # => warning: already initialized constant CONST_A 13 | puts CONST_A # 'bar' 14 | ---- 15 | 16 | 另一个常常被人混淆的概念则是 Ruby 中的 `Object#freeze` 方法。「把变量冻结后我就不能编辑了,这难道不是常量吗?」 17 | 18 | [source,ruby] 19 | ---- 20 | a = 'foo'.freeze 21 | a << 'bar' # FrozenError (can't modify frozen String: "a") 22 | ---- 23 | 24 | 这同样不是常量,而是另一个名词「不变量(immutable)」。我们可以看下面的例子,只要不是编辑对象本身,我依然可以给冻结后的对象赋予一个完全的新值。 25 | 26 | [source,ruby] 27 | ---- 28 | a = 'foo'.freeze 29 | a = 'bar' 30 | puts a # 'bar' 31 | ---- 32 | 33 | 虽然可变量(mutable)、不变量(immutable)听起来和变量(variable)以及常量(const)听起来非常像,但它们是完全不一样的东西。我们要把数据的存储拆成两个部分来看,代号和内容。变量、常量说的是代号所指的对象的可变还是不可变;而可变量、不变量指的是指向的那个对象里面内容物的可变还是不可变。 34 | 35 | 我们举个例子,现在有一座房子,地址是 A,A 就是变量名。A 房子里住着小明。如果小明可以把这 A 房子卖给小王,那么这个 A 房子就是个「变量」;如果小明不被允许把 A 房子卖给其他人,那么 A 就是一个「常量」。A 房子里的小明体重 100 斤,今天小明吃成了 200 斤,那么他就是个「可变量」;如果小明住进房子里,所有性状不可能变化了,那么他就是个「不变量」。至于今天 A 房子里住着一个 100 斤的小明,我明天把 A 房子转给另一个 200 斤的小王住,那就不管「可变/不变」什么事,而是「变量/常量」的事情了。 36 | 37 | 简而言之,「可变/不变」是数据结构本身的特性,而「变量/常量」是指向数据的代号的特性。 38 | 39 | 讲到这里结论就已经很明显了:Ruby 没有常量,全大写的常量其实本质上还是变量只是会在改变时抛出 warning,而 freeze 是将可变量转换成不可变量。 40 | -------------------------------------------------------------------------------- /chapter01/editor.adoc: -------------------------------------------------------------------------------- 1 | === REPL 和编辑器 2 | 3 | ==== REPL 4 | 5 | 使用 `info irb` 来查看 `irb` 命令的帮助,我们会看到 `irb` 是「交互式 Ruby 壳程序」(Ruby Interactive Ruby Shell)的缩写,所谓的交互式就是 Ruby 程序的 REPL 环境。 6 | 7 | REPL 是一个在 Lisp 和受其影响的语言中非常常见的概念,是「读取-求值-输出 循环」(Read-Eval-Print Loop)的缩写。启动 REPL 程序后,你可以立刻运行你的各种代码,而无需新建一个文件编辑后再去执行。这对于测试、调试非常实用。 8 | 9 | 对于 Ruby 来说,在终端程序中输入 `irb` 按下回车后,Ruby REPL 程序就会启动。 10 | 11 | [source] 12 | ---- 13 | ❯ irb 14 | 2.7.0 :001 > 15 | ---- 16 | 17 | 在这个环境里输入 `puts 'Hello World'` 然后回车,就会立刻看到屏幕上打印了 Hello World。Hello World 两侧被「单引号(')」包围,来告诉电脑这里是一个字符串,而不是代码。**注意:这里的单引号是英文输入法下的傻瓜单引号('),而不是中文的单引号(‘’)** 18 | 19 | [source] 20 | ---- 21 | ❯ irb 22 | 2.7.0 :001 > puts 'Hello World' 23 | Hello World 24 | => nil 25 | 2.7.0 :002 > 26 | ---- 27 | 28 | 在这个环境里输入 `exit` 然后回车则会退出。 29 | 30 | REPL 叫「读取-求值-输出 循环」,我们就来分别看一看这四个步骤: 31 | 32 | 1. 读取:REPL 程序启动后,就会等待你输入新的代码。 33 | 2. 求值:当输入 `puts 'Hello World'` 后,程序就会进行求值。`puts` 方法接收一个字符串(一串字符)作为参数,在屏幕上打印出来并自动换行。然后返回值为「空」。 34 | 3. 输出:REPL 会自动把求值结果输出打印到屏幕。由于 `puts` 的返回值是「空」,所以这里打印 `nil`。 35 | 4. 循环:完成输出后回到等待读取状态,等待下一条代码的输入。 36 | 37 | ==== 豆知识:`nil` 还是 `null`? 38 | 39 | Ruby 在返回值是「空」时,返回 `nil`。你可能在其它语言中会发现会用 `null` 或者 `NULL` 关键字来表示类似的含义。事实上,它们确实是相同的。这两个词语都是拉丁语词源的。 40 | 41 | `nil` 来自于拉丁语 nīl,是 nihil, nihilium 的缩写。其中 ni 是 ne- 词缀的变形,表示「没有 (not)」。而 hilium 是「一点点 (a little, a trifle)」。合起来「没有一点点」,就是「完全没有」的意思。 42 | 43 | `null` 来自法语 nul,而 nul 来自于拉丁语 nūllus。其中 n 同样是 ne- 词缀的变形,表示「没有 (not)」。而 ūllus 则是「任何(any)」的意思。合起来「任何一个都没有」,同样也是「完全没有」的意思。 44 | 45 | ==== 编辑器 46 | 47 | 如果要写一个复杂的程序,使用 REPL 可能还是太过牵强了。我们还是要准备一个编辑器,提前编辑好文件再来执行它。事实上,任何纯文本编辑器,包括 Windows 自带的那个记事本,都可以被用来编辑 Ruby 代码。但是,我们没有必要折磨自己,写代码应该是一件让人开心的事情。使用专门为代码编辑设计的编辑器对于程序开发还是有很多好处的。下面是一些代码编辑器会提供的关键特性: 48 | 49 | ===== 等宽字体 50 | 51 | 代码编辑器通常都会默认使用等宽字体作为默认字体,其特点是无论是拉丁字母还是常用的西文符号的字符宽度都是一致的。 52 | 53 | image::font-preview.png[Font Preview] 54 | 55 | 在这张图中,上方的思源黑体 **不是** 一款等宽字体,字形的宽度会变化。而下方的 https://github.com/be5invis/Iosevka/[Iosevka] 字体是一款等宽字体,字形的宽度是固定的。如果是阅读长篇文章,非等宽字体也许会更加美观好读。但是代码是具备功能性的问题,有时代码的对齐对于调试时的可读性有很大的帮助,所以 **强烈推荐** 使用等宽字体来进行编程。 56 | 57 | 对于 CJK(中文、日文、韩文)开发者,我个人非常推荐使用 https://github.com/be5invis/Sarasa-Gothic[更纱黑体] 作为编辑器的字体。更纱黑体是 Iosevka 和思源黑体的结合字体。因为 Iosevka 字体设计的宽度正好是思源黑体中汉字、平假名、片假名和谚文宽度的一半,即使文字混合同样可以对齐。 58 | 59 | ===== 语法高亮 60 | 61 | 语法高亮指的是通过不同颜色来标记代码中不塌缩的关键字从而提高代码的可读性。比如下面两段一样的代码: 62 | 63 | [source] 64 | ---- 65 | def abs(num) 66 | return num if num > 0 67 | -num 68 | end 69 | ---- 70 | 71 | [source,ruby] 72 | ---- 73 | def abs(num) 74 | return num if num > 0 75 | -num 76 | end 77 | ---- 78 | 79 | 显然下面那种读起来更加舒适。花花绿绿是一种硬需求,以至于在 Ruby 2.7 的 `irb` 中,默认也支持了语法高亮。 80 | 81 | ===== 代码补全 82 | 83 | 代码补全指的是编辑器能根据你输入一半的关键字来自动推测你的代码,从而给予智能的补全提示。这在一些静态类型语言中会有相当高的准确率,在一些 API 名称长度非常长的语言或框架(例如 Java 或苹果的 Cocoa 框架)中会显得特别实用: 84 | 85 | [TIP] 86 | .XCode 中 Swift 语言的自动代码补全 87 | ==== 88 | image::xcode-autocomplete.png[] 89 | ==== 90 | 91 | Ruby 是一门动态语言,大多数的代码补全的准确率没有那么好,目前有一些基于人工智能的全新尝试,例如「TabNine」代码补全插件,但还在比较早期的阶段。好在 Ruby 的 API 通常都比较短,也不算是太大的问题。 92 | 93 | ===== 项目组织管理 94 | 95 | 虽然文件管理在早年的编辑器中不是必需。但在今天工程复杂度越来越大的大前提下变得越来越重要。内置文件、目录的管理,甚至是和 `git` 等版本管理工具进行结合对于提升代码开发效率也有着相当的帮助。 96 | 97 | ==== 编辑器选择 98 | 99 | 现代的代码编辑器,我个人推荐使用 https://code.visualstudio.com/[Visual Studio Code]。Visual Studio Code(以下简称 vscode)是微软近年来推出的罕见在口碑上成功的产品。vscode 是一款开源、跨平台的文本编辑器。其本身非常轻量,安装非常方便,同时也有丰富的插件系统支持。只需安装 https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby[Ruby 插件],即可满足 Ruby 开发大多数的需求。和其主要竞争者 Atom 比较,vscode 的插件系统实现非常高效,编辑器不容易被插件拖累性能而变得卡顿。 100 | 101 | image::vscode.png[] 102 | 103 | 纯命令行(CLI)下的代码编辑器,Vim 和 Emacs 都有其非常坚持的使用者。这两款编辑器仿佛两个宗教,在网上时不时都会引来支持者们疯狂地辩论。但不得不说,这两款编辑器都有着非常高的效率(如果你熟悉各种快捷键和插件配置的话),但这两款编辑器同样都有着极高的学习曲线。如果你考虑把终端作为自己的主要使用应用的话,熟悉其中的一款是非常有效的。但如果你只是临时、极少使用终端上的文本编辑器的话,使用操作傻瓜 `nano` 也不失为一个好选择。 104 | 105 | 另一个选项则是 IDE,IDE 是集成开发环境(Integrated Development Environment)的缩写。代码补全、整合的调试开发环境、项目组织管理、版本管理曾经是 IDE 最明显的特点。但现代的文本编辑器像是 vscode 同样支持了这些特性,让编辑器和 IDE 之间的界限愈发模糊。如果想用 IDE 开发 Ruby,目前最好用的工具是由 JetBrains 推出的 https://www.jetbrains.com/ruby/[RubyMine]。RubyMine 对 Ruby 上常见框架都实现了自己的类型补充,使得代码自动补全变得比较可用。同时 JetBrains 祖传的重构(Refactor)工具对于大型项目的开发也是比较高效的工具。 106 | 107 | ==== 试一试 108 | 109 | 本文使用 vscode 作为编辑器的例子,其它编辑器同理。创建一个文件,保存命名为 `hello.rb`(`.rb` 是 Ruby 的默认后缀名)。在里面输入如下的代码: 110 | 111 | [source,ruby] 112 | ---- 113 | puts 'Hello World' 114 | ---- 115 | 116 | 打开终端应用(Windows 下是 `cmd`),利用 `cd 路径` 切换至这一目录,使用 `ruby hello.rb` 执行。 117 | 118 | [source] 119 | ---- 120 | ❯ ruby hello.rb 121 | Hello World 122 | ---- 123 | 124 | 你便成功运行了这一 Ruby 文件。尝试改一改这一 Ruby 文件,再多运行几次来体验一下 Ruby 程序的运行吧。 125 | 126 | [NOTE] 127 | .小练习 128 | ==== 129 | 1. 在屏幕上打印你的名字。 130 | 2. 分两行分别打印你的「姓」和「名」。 131 | ==== 132 | 133 | [TIP] 134 | .豆知识:Ruby 中打印输出有哪些常用方法? 135 | ==== 136 | 在上文中我们使用的是 `puts` 命令来打印的。Ruby 中常用的打印输出到屏幕的方法有: 137 | 138 | - `puts` 139 | - `print` 140 | - `p` 141 | 142 | `puts` 和 `print` 的差异是显而易见的,`puts` 会自动在打印完成后换行,而 `print` 不会。`p` 则比较复杂,`p foo` 类似于 `puts foo.inspect`,`#inspect` 是 Ruby 中查看某一个对象内部结构的方法。比如: 143 | 144 | [source,ruby] 145 | ---- 146 | p 'Hello World' 147 | ---- 148 | 149 | 打印的是 "Hello World" 而不单单是 Hello World。这一对引号即强调了这个对象是一个字符串,串的内容是 Hello World。另外一个差异是,`puts` 和 `print` 都是返回 `nil` 的,但 `p` 会原样返回,这一特性可以用来非常方便地调试程序故障。 150 | 151 | 猜一猜运行下面的代码,打印结果是什么? 152 | 153 | [source,ruby] 154 | ---- 155 | print 'Hello' 156 | puts 'Hello' 157 | p 'Hello' 158 | ---- 159 | 160 | 运行一下看看和自己猜的是不是一样,并尝试来解释一下为什么。 161 | ==== 162 | -------------------------------------------------------------------------------- /chapter01/env.adoc: -------------------------------------------------------------------------------- 1 | === 环境搭建 2 | 3 | ==== 了解 Ruby 解释器 4 | 5 | ===== 什么是解释器? 6 | 7 | 开发 Ruby 程序最重要环境的是 Ruby 解释器。「解释器」是在解释什么?解释前和解释后的东西是什么? 8 | 9 | 了解解释器,我们需要先了解另一个相近的概念「编译器」。在上世纪计算机刚刚出现的时候,程序员编程需要依赖纸带打孔卡或类似的穿孔纸带。 10 | 11 | image::punch-card.png[] 12 | 13 | 这里的打孔与不打孔就被作为一系列「开」和「关」信号传给电脑,形成程序交由计算机执行。今天的计算机已经淘汰了打孔纸带,计算机的启动和运行流程也变得越来越复杂,但程序依然是一系列「开」和「关」(1 或 0)的二进制信号。直接编写这些二进制,对于今天越来越复杂的程序开发要求来说,已经变得非常困难,于是我们发明了更接近于自然语言的高级语言。Ruby 就是这样一门高级语言。 14 | 15 | 高级语言不能直接被计算机执行,但是我们可以先通过一个「编译器」程序,读入这些高级语言,然后翻译成机器可以执行的二进制。「编译器」通常会把整个程序在运行前就完全编译到机器可以执行的二进制。「解释器」会在运行过程中,把程序一行一行直接翻译执行。解释器通常会比编译器执行得更慢,但同样也带来了更大的动态特性,允许更自由地开发方式。而 Ruby 的执行环境,通常就是这样一个「解释器」。 16 | 17 | ===== Ruby 解释器有什么? 18 | 19 | Ruby 解释器是 Ruby 语言开发的核心。Ruby 是一个开放的语言,任何人都可以为 Ruby 实现自己的解释器。Ruby 的解释器多种多样,常见的仍在维护的解释器有: 20 | 21 | * Ruby 22 | ** Ruby MRI (CRuby) 23 | ** JRuby 24 | ** TruffleRuby 25 | ** Rubinius 26 | ** RubyMotion 27 | ** Opal 28 | * mruby 29 | ** mruby 30 | ** mruby/c 31 | 32 | 这些 Ruby 解释器各有一些差异,支持的语法和执行的性能也并不完全相同。本书所涉及的全部 Ruby 都指官方实现的 Ruby MRI。 33 | 34 | MRI 代指 Matz's Ruby Interpreter,即 Ruby 创始人松本行弘最早实现的 Ruby 解释器,是 Ruby 的官方解释器。虽然在 Ruby 1.9 之后的版本中,官方已经把虚拟机换成了由 Koichi Sasada 主导的 YARV (Yet another Ruby VM) 解释器。但在 1.9 版本后,YARV 已经被合并到 MRI,此后我们已不特别区分 MRI 和 YARV 了,现在仍称呼这一解释器实现为 Ruby MRI。下文所有的 Ruby 解释器,如无特殊标注,都是 Ruby MRI 解释器的缩写。 35 | 36 | 在撰写本章节的时候,Ruby 的最新版本是 2.7.0,本书全部的代码都在 2.7.0 中进行过测试。我们会自动化测试本书代码在不同环境下的兼容性,以确保正确性。 37 | 38 | ==== 安装方法 39 | 40 | ===== Windows 用户 41 | 42 | 在 Windows 上安装 Ruby 最简单的方式是 https://rubyinstaller.org/[RubyInstaller]。在本书的编写过程中,我们会测试书中所有涉及到的程序在 Windows 上的兼容性。但由于 Ruby 的第三方依赖,特别是一些设计给 *NIX 服务器的依赖程序,可能没有测试在 Windows 上的兼容性,从而可能在使用上会遇到一定的困难。 43 | 44 | 一些教程不推荐新手在 Windows 上开发 Ruby。但事实上,Ruby 的标准库对于 Windows 的兼容性还是相当良好的。如果没有人在 Windows 上使用 Ruby,那么 Ruby 运行在 Windows 上的问题会变得更多,这是一个恶性循环。但是对于初学者,使用 Linux/macOS 进行开发依然是我个人推荐的。主要问题是,初学者缺乏对环境问题处理的经验,遇到问题往往会不知所措。大多数服务器软件的生产环境更愿意使用自由的 Linux 操作系统,而使用 Ruby 开发服务器应用是最常见的用途,使用和生产环境一致的环境,至少是 *NIX 环境能有效避免问题发生的概率。 45 | 46 | 关于 PC 用户如何选择和安装 Linux 发行版,本书单独开设附录章节来描述,请参阅《附录一:新手如何安装 Linux 开发版?》。另外,在 Windows 10 中可以使用 Windows Subsystem for Linux (WSL) 来产生一个无缝的 Linux 环境。详情请参阅微软的 https://docs.microsoft.com/en-us/windows/wsl/about[WSL 官方文档]。 47 | 48 | ===== *NIX 用户(Linux、macOS、BSD 用户) 49 | 50 | ====== RVM 51 | 52 | RVM 是最推荐新手安装 Ruby 的方法。笼统来说,使用 RVM 安装 Ruby 需要三步。如果你是 macOS 用户,你可能需要先安装 XCode 和 XCode Command Line Tools 才能安装 RVM。 53 | 54 | XCode 可以从 App Store 直接下载到,安装完成后打开「终端(Terminal)」应用,使用 `xcode-select --install` 安装 XCode Command Line Tools。 55 | 56 | 对于 Linux 用户,请确认自己的 Terminal 是 login shell 的模式。一些 Linux 发行版自带的 Terminal 应用没有 login,可能没有加载用户的环境变量。 57 | 58 | 第一步是获取 GPG 公钥,RVM 使用 GPG 密钥系统来确保程序在传输过程中不被篡改。打开终端应用,输入下面的命令即可获取 GPG 密钥。 59 | 60 | [source,bash] 61 | ---- 62 | gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB 63 | ---- 64 | 65 | 第二步则是一键安装脚本,下面的命令会下载 RVM。 66 | 67 | [source,bash] 68 | ---- 69 | \curl -sSL https://get.rvm.io | bash -s stable 70 | source ~/.rvm/scripts/rvm # 载入 RVM 环境(新开 Terminal 就不用这么做了,默认自动重新载入的) 71 | ---- 72 | 73 | 对于中国大陆地区用户,RVM 的自带镜像源可能下载速度太慢。为此 Ruby China 提供了镜像源,在执行第三步前可以使用 74 | 75 | [source,bash] 76 | ---- 77 | echo "ruby_url=https://cache.ruby-china.com/pub/ruby" > ~/.rvm/user/db 78 | ---- 79 | 80 | 来切换镜像源。 81 | 82 | 最后一步就是执行下面命令来安装特定版本的 Ruby。 83 | 84 | [source,bash] 85 | ---- 86 | rvm install 2.7.0 # 这里的 2.7.0 可以切换成阅读本文时最新的 Ruby 版本。Ruby 最新版本可以在 https://www.ruby-lang.org/ 确认。 87 | ---- 88 | 89 | 安装成功后可以使用 90 | 91 | [source,bash] 92 | ---- 93 | ruby -v 94 | ---- 95 | 96 | 来检查所安装的 Ruby 版本有没有正确安装成功,如果返回了版本号,那么就是安装成功了。 97 | 98 | 详细的安装文档可以在 https://rvm.io/[rvm.io] 来查询。 99 | 100 | ====== snap 101 | 102 | Ruby 在 2018 年 11 月 8 日加入了官方的 snap 套件支持。如果你使用 Ubuntu 16.04 的后续版本,你可以一键安装 Ruby。 103 | 104 | [source,bash] 105 | ---- 106 | sudo snap install ruby --classic 107 | ---- 108 | 109 | 使用 Snap 安装的 Ruby 可能会在环境变量上需要一些额外的配置。建议检查官方的 https://www.ruby-lang.org/zh_cn/news/2018/11/08/snap/[新闻] 来确认细节。 110 | 111 | ====== 编译自源代码 112 | 113 | CAUTION: 编译自源代码是较为困难的安装方法,不建议新手使用这一方式。 114 | 115 | 在电子游戏《尼尔:机械纪元》的 https://www.jp.square-enix.com/nierautomata/sp/lisence/[用户协议] 中,我们会发现出现了 Ruby License。可见在这款游戏中使用了 Ruby 语言实现了一定的功能。这款游戏首发在 PS4 平台上,而 PS4 的操作系统是一个修改自 FreeBSD 操作系统。所以 Ruby 语言对于 BSD 系的操作系统同样是非常友好的。 116 | 117 | 但如果你想在一些嵌入式设备上运行 Ruby 或者需要运行在 PS4 上,使用包管理器可能不是一个好主意,因为你不一定具有全局安装的权限或者不想引入额外的复杂度。这时候直接从源代码编译可能就变成了必须。 118 | 119 | 120 | [TIP] 121 | .生日快乐!欢迎自己编译你的蛋糕。 (Photo by Monika Grabkowska on Unsplash) 122 | ==== 123 | image::cake-recipe.jpg[] 124 | ==== 125 | 126 | 从源代码编译安装很简单,你可以先从 Ruby 官方网站下载 https://www.ruby-lang.org/zh_cn/downloads/[最新的源代码],在解压后执行: 127 | 128 | [source,bash] 129 | ---- 130 | ./configure 131 | make 132 | ---- 133 | 134 | 进行编译。Ruby 的编译过程中,一些组件是可选的,你需要自己确认这些可选的依赖是否准备妥当。如果编译后需要安装,你可以执行: 135 | 136 | [source,bash] 137 | ---- 138 | sudo make install 139 | ---- 140 | 141 | 进行安装。 142 | 143 | ===== 内置包管理器 144 | 145 | 使用例如 Ubuntu、Debian 内置的 `apt` 或者 CentOS、Fedora 内置的 `dnf` 或类似方法也可以很方便安装 Ruby。但是,大多数操作系统内建的软件源常有版本滞后、缺少组件的问题。如无必要,不推荐新手使用这样的安装方法。但如果你有一个可控、可信、维护良好的软件源,这也是一个不增加复杂度安装 Ruby 的好方法。 146 | -------------------------------------------------------------------------------- /chapter01/functions.adoc: -------------------------------------------------------------------------------- 1 | === 函数基础 2 | 3 | Ruby 中的函数 (function) 的本质其实是方法 (method)。关于这两者的差异,我们会在《第四周:面向对象再入门》中再详述。我们在这一章节中还是作为函数来处理。 4 | 5 | ==== 什么是函数? 6 | 7 | 理解什么是程序中的函数,我们先理解什么是数学中的函数。在小学和初中中学习的函数通常是比较狭隘的,往往把函数和表达式画上等号。但站在集合论的角度,我们可以更好理解函数的本质。我们把定义域和值域看成两个集合,函数就是定义域和值域的对应关系。 8 | 9 | 如果我们用比较粗糙的观点来看,函数 latexmath:[f] 就是一个黑盒 (black box)。当你扔进去一个定义域里的 latexmath:[x],函数就会返回一个经过函数处理的结果 latexmath:[y],即 latexmath:[y = f(x)]。程序中的函数也是这样一个东西,但是和数学中的函数也有一些细微差异。 10 | 11 | 目的上来说,程序中的函数和数学上的函数是一致的。在数学中,函数的引入极大简化了表达。重复的运算可以由函数或函数的组合来表达,同时我们也可以对函数本身进行进一步的研究(比如导数)。程序中也是同理。程序的开发是为了解决人类的重复性工作,其代码过程中也必然会有大量重复的,需要复用的地方。而函数就为这种场景提供了绝佳的解决方案。 12 | 13 | 但程序中的函数和数学上的函数还存在一定差异。对于一个数学函数,对于一个给定的 latexmath:[x],无论传入多少次,其结果都是不变的。但这一行为在大多数编程语言中通常是不被保证的,即这一函数函数是不是纯函数 (pure function)。我们会在之后的例子中进一步讨论这一问题。 14 | 15 | [.line-through]#当我们把需求、比萨和咖啡扔给程序员,程序员就会输出程序。# 16 | 17 | [.line-through]#从这个角度上来看,程序员不也是一个函数吗?# 18 | 19 | ==== 函数定义 20 | 21 | 函数的三大要素:定义域、值域和映射方法。 22 | 23 | 对应到程序中就是参数、返回值和函数代码片段。我们也从这三块来理解 Ruby 中的函数定义。 24 | 25 | [source,ruby] 26 | ---- 27 | def succ(x) # <- 函数的名称与参数 28 | x + 1 # <- 函数的返回值 29 | end # <- 函数代码结束 30 | 31 | succ(1) # => 2 32 | ---- 33 | 34 | Ruby 的函数以 `def` 开始,`def` 是 define 的缩写,即要在此定义函数。 35 | 36 | `def` 后接一个空格,然后是函数的名称,这里例子中是 `succ`,即之后要用 `succ` 这个名字来调用这一函数。 37 | 38 | 函数名后紧跟一对括号,括号内是参数列表。函数的定义域在程序中就是以「参数 (arguments)」来实现的。如果没有参数,可以省略这一对括号。例如: 39 | 40 | [source,ruby] 41 | ---- 42 | def hello 43 | puts 'Hello' 44 | end 45 | ---- 46 | 47 | 括号内的参数是一些变量名,外面传入的参数就会在这里以这些变量被传入函数内。如果需要传入多个参数,则以逗号 `,` 分割。 48 | 49 | [source,ruby] 50 | ---- 51 | def add(a, b) 52 | a + b 53 | end 54 | 55 | add(1, 2) # => 3 56 | ---- 57 | 58 | 函数的映射方法是通过函数代码来实现的。参数列表后,`end` 关键字前即为函数代码,函数代码可以利用函数中的变量进行一些变换从而实现函数的功能。而最后一步,则是将函数的计算结果传回给函数的调用。函数的值域是通过「返回值 (return value)」来实现的。 59 | 60 | 在 Ruby 中函数有两种方法定义返回值。 61 | 62 | - 使用 return 来返回值 63 | - 默认将最后一次运算结果返回 64 | 65 | 在下面这个例子中: 66 | 67 | [source,ruby] 68 | ---- 69 | def add(a, b) 70 | a + b 71 | end 72 | 73 | add(1, 2) # => 3 74 | ---- 75 | 76 | 函数的第一行也是最后一行代码是 `a + b`,Ruby 会自动将这最后的计算结果作为返回值。 77 | 78 | 而类似于其它编程语言,Ruby 也支持使用 `return` 关键字来返回值。同时 `return` 也意味着程序的提前运行,这在我们之后讲到流程控制时会非常有用。 79 | 80 | [source,ruby] 81 | ---- 82 | def add(a, b) 83 | return a + b 84 | puts 'wat?' # 这一行不会被运行 85 | end 86 | ---- 87 | 88 | ==== 函数调用 89 | 90 | 调用自己定义的函数如同我们之前调用过的所有系统内建的函数一样,直接函数名加上参数即可。 91 | 92 | [source,ruby] 93 | ---- 94 | def add(a, b) 95 | return a + b 96 | end 97 | 98 | add(1, 1) # => 2 99 | add 1, 1 # => 2 100 | ---- 101 | 102 | 在许多编程语言中,参数必须被 `()` 包裹住。在 Ruby 中,如果嵌套关系明确,那么 `()` 也可以省略。这也是为什么我们之前打印内容到屏幕时可以写成 `puts 'Hello World'`,这其实和 `puts('Hello World')` 等价。 103 | 104 | [TIP] 105 | .豆知识:`void` v.s. `nil` 106 | ==== 107 | Ruby 和其它语言还有一个在函数返回值上的差异。在 Ruby 中任何方法都有「返回值」,但返回值可能为「空」。这句话听起来非常拗口,我们来简单比较一下。如果我们在 C++ 语言中定义一个函数如下: 108 | 109 | [source,c++] 110 | ---- 111 | void void_function() { 112 | return; 113 | } 114 | 115 | int main() { 116 | auto x = void_function(); // illegal, 非法代码。 117 | return 0; 118 | } 119 | ---- 120 | 121 | 这段代码是非法的,因为 `void_function()` 没有返回值,不能让其结果赋值给 `x`。 122 | 123 | 但是如果在 Ruby 中定义一个空函数, 124 | 125 | [source,ruby] 126 | ---- 127 | def nil_func 128 | return 129 | end 130 | 131 | x = nil_func 132 | p x # => nil 133 | ---- 134 | 135 | 但是我们保持语义改用 Ruby 来写。`nil_func` 函数确实是有返回值的,只不过这个返回值是 `nil` 而已。Ruby 中的 **任何函数都有返回值**。 136 | ==== 137 | 138 | [NOTE] 139 | .小练习 140 | ==== 141 | 1. 创建一个函数 `pow`,接受两个参数 `x` 和 `y`,计算 latexmath:[x^y] 的结果并返回。 142 | 2. 创建一个函数 `succ`,接受一个参数 `x` 返回 `x + 1`。定义另一个函数 `succ2`,在不使用 `+` 运算符的前提下,返回 `x + 2`。 143 | ==== 144 | 145 | [TIP] 146 | .豆知识:定义好的函数可以取消吗? 147 | ==== 148 | 虽然这样的需求很少会发生,但 Ruby 还真的支持取消定义好的函数的特性。可以试一试下面的代码: 149 | 150 | [source,ruby] 151 | ---- 152 | def foo 153 | 'bar' 154 | end 155 | 156 | foo # => 'bar' 157 | 158 | undef foo 159 | 160 | foo # => NameError 161 | ---- 162 | ==== 163 | -------------------------------------------------------------------------------- /chapter01/rvm.adoc: -------------------------------------------------------------------------------- 1 | === 如何管理 Ruby 版本 2 | 3 | 使用 RVM 的一个好处是可以很好管理不同的 Ruby 版本。刚开始写 Ruby 的时候可能不太会意识到这件事情的重要性。但是下面这几种需求在实际开发中可能会发生: 4 | 5 | - Ruby 版本更新了,怎么升级? 6 | - 想临时测试某一特定版本 Ruby 的特性,怎么临时切换? 7 | - 如何指定项目的 Ruby 版本,来确保服务器运行环境和开发环境一致? 8 | 9 | ==== 如何安装新的 Ruby 版本? 10 | 11 | 在安装之前,你可以先用下面命令检查可以安装的 Ruby 版本: 12 | 13 | [source,bash] 14 | ---- 15 | rvm list known 16 | ---- 17 | 18 | 如果你想要安装的版本没有出现在这个列表中,你也许需要更新 RVM。更新 RVM 的方法非常简单: 19 | 20 | [source,bash] 21 | ---- 22 | rvm get head 23 | ---- 24 | 25 | 和你安装第一个 Ruby 版本一样,安装新的 Ruby 版本的方法完全一样,比如我想安装 2.7.0 版本,就可以使用下面命令。 26 | 27 | [source,bash] 28 | ---- 29 | rvm install 2.7.0 30 | ---- 31 | 32 | ==== 如何切换 Ruby 版本? 33 | 34 | 如果一个常见的需求是安装新 Ruby 版本后希望把默认 Ruby 版本切换到新安装的版本上,命令则是: 35 | 36 | [source,bash] 37 | ---- 38 | rvm --default use 2.7.0 39 | ---- 40 | 41 | 如果你只是需要在当前 Shell 环境下临时切换,不需要设置成默认,只要把 `--default` 参数拿掉即可: 42 | 43 | [source,bash] 44 | ---- 45 | rvm use 2.7.0 46 | ---- 47 | 48 | 切换后,可以用 49 | 50 | [source,bash] 51 | ---- 52 | ruby -v 53 | ---- 54 | 55 | 确认切换后的 Ruby 版本。 56 | 57 | ==== 如何设置项目的特定 Ruby 版本? 58 | 59 | 在项目根目录下放置一个名称为 `Gemfile` 的文件,并在里面写入如下的内容: 60 | 61 | [source,bash] 62 | ---- 63 | ruby '2.7.0' 64 | ---- 65 | 66 | RVM 就会在 shell 切换到这个目录下之后自动切换当前的 Ruby 版本。这样的设置还有一个好处,就是著名的 Ruby 托管平台 https://heroku.com[Heroku] 也是使用这一方法来切换 Ruby 版本的。如果之后你需要将自己制作的网站托管到 Heroku 上的话,可以利用这一特性自动设置 Heroku 上的 Ruby 版本。 67 | 68 | Gemfile 是 Bundler 提供依赖管理的重要文件,有关于这方面的功能,我们会在「第六周:Ruby 工程化入门」中重点介绍。 69 | 70 | 由于 Ruby 在设置版本上并没有官方制定的标准。管理项目版本依赖于不同版本管理工具的具体实现。在 RVM 中除了上面的方法,还有 4 种方法可以设置项目的 Ruby 版本。具体可以参考 RVM 的 https://rvm.io/workflow/projects[Typical RVM Project Workflow]。其中使用 `.ruby-version` 设置 Ruby 版本的方法可以同时在 RVM 和另一个在 Ruby 上常用的版本管理工具 rbenv 通用。 71 | -------------------------------------------------------------------------------- /chapter01/sugar.adoc: -------------------------------------------------------------------------------- 1 | === 语法糖 2 | 3 | Ruby 喜欢语法糖(syntactic sugar)。 4 | 5 | 在计算机科学中,语法糖是一类语法,这类语法对于语言的功能不产生任何影响。但是提高代码的可读性,从而更方便程序员使用。Ruby 提供了一些语法,使得其阅读起来更接近于自然语言。比如下面的代码: 6 | 7 | [source,ruby] 8 | ---- 9 | stop = false 10 | 11 | if not stop 12 | puts 'Go!' 13 | end 14 | ---- 15 | 16 | 在自然语言中,我们很少会使用 if not 这样的语法,而是会使用 `unless`。Ruby 中也提供了 `unless` 关键词,作为 if not 的替代。于是你可以把代码写成下面这样: 17 | 18 | [source,ruby] 19 | ---- 20 | stop = false 21 | 22 | unless stop 23 | puts 'Go!' 24 | end 25 | ---- 26 | 27 | 在 Ruby 中,如果条件判断内只有一行,你可以把条件判断放在你要执行的代码后面,从而写成: 28 | 29 | [source,ruby] 30 | ---- 31 | stop = false 32 | 33 | puts 'Go!' unless stop 34 | ---- 35 | 36 | 这样读起来确实变得更接近自然语言了。类似地,对于 `while not` 循环,你可以用 `until` 关键词来替代: 37 | 38 | [source,ruby] 39 | ---- 40 | a = 5 41 | 42 | until a == 0 43 | a = a - 1 44 | end 45 | ---- 46 | 47 | 同样,你也可以简化成一行: 48 | 49 | [source,ruby] 50 | ---- 51 | a = 5 52 | 53 | a = a - 1 until a == 0 54 | ---- 55 | 56 | 对于四则运算,一个常见的需求就是累加累减,也就是说,对于某一变量计算的结果会存回变量本身。对于这一点 Ruby 也提供了语法糖,即可以使用 57 | 58 | [source,ruby] 59 | ---- 60 | a += b 61 | a -= b 62 | a *= b 63 | a /= b 64 | ---- 65 | 66 | 来替代 67 | 68 | [source,ruby] 69 | ---- 70 | a = a + b 71 | a = a - b 72 | a = a * b 73 | a = a / b 74 | ---- 75 | 76 | 于是我们可以把上面的代码简化成: 77 | 78 | [source,ruby] 79 | ---- 80 | a = 5 81 | 82 | a -= 1 until a == 0 83 | ---- 84 | 85 | [WARNING] 86 | .特别注意 87 | ==== 88 | 如果你是 C 语言或者 {cpp} 语言的使用者,那么你必然还熟悉另一种累加累减运算符 `a\++` `++a` `a--` `--a`。但是在 Ruby 中没有对应语法。 89 | ==== 90 | 91 | 对于死循环(infinite loop),你还可以使用 `loop` 来替代 `while true`: 92 | 93 | [source,ruby] 94 | ---- 95 | loop do 96 | puts 'Hello World' 97 | end 98 | ---- 99 | 100 | ==== 可计算性与语法设计 101 | 102 | CAUTION: 本章节涉及较为进阶内容,建议第一次接触编程或缺乏编程经验的开发者暂时跳过这一章节。 103 | 104 | 事实上,有了变量赋值和条件、循环(或者跳转),如果不考虑交互和通讯,就已经可以解决了所有计算机可以计算的问题。关于这一方面的研究被称为可计算性理论(Computability Theory)。20 世纪上半叶,对于可计算性的公式化表达主要有三: 105 | 106 | - 阿隆佐·邱奇提出的 Lambda 演算。 107 | - 阿兰·图灵提出的通用图灵机。 108 | - 邱奇、克莱尼和罗斯塞尔提出的递归算法。 109 | 110 | 现在已经证明,这三种模型的可计算性上是等价的。在今天的高级语言中,语言通常会提供高于这三者的抽象。以 Ruby 为例,Ruby 同时提供偏向于图灵机设计理念的变量、分支系统,提供接近 Lambda 演算概念的函数式编程泛型,也支持递归函数定义。但即使如此,Ruby 编程语言的可计算性并不比只提供一种模型强。今天的计算机和 20 年前的计算机,也没有可计算性上的差异,只是存储更大、运行速度更快而已。这如同标准大气压下三杯 100℃ 的开水,把三杯水倒在一起,温度还是 100℃。但是不同语言确实会有不同的抽象,语言提供丰富的抽象的目的主要有两点: 111 | 112 | - 更适合现代计算机系统的设计,从而提高性能。 113 | - 更符合人类直觉,从而增加代码的可读性。 114 | 115 | ===== 为性能服务的语法设计 116 | 117 | 比如我们给定一个数组和范围,让程序计算其平方,在 Ruby 中可以写成: 118 | 119 | [source,ruby] 120 | ---- 121 | def square(arr, a, b) 122 | (a...b).each do |i| 123 | arr[i] = arr[i] ** 2 124 | end 125 | end 126 | ---- 127 | 128 | 这一程序可以同时对整数和小数作用。不同于 Ruby,像 C++ 之类的语言就要求必须要在编译器定义数据的类型。比如下面的程序只能对浮点数有效: 129 | 130 | [source,c++] 131 | ---- 132 | void square(float *A, int start, int end) { 133 | for (int i = start; i < end; ++i) { 134 | A[i] = A[i] * A[i]; 135 | } 136 | } 137 | ---- 138 | 139 | 但如果我们检查 C++ 编译器(此处使用 LLVM 9.0 (x86-64),编译参数为 `-O2`)我们可以看到如下的交给 CPU 执行的汇编代码: 140 | 141 | [source,asm] 142 | ---- 143 | square(float*, int, int): # @square(float*, int, int) 144 | cmp esi, edx 145 | jge .LBB0_11 146 | movsxd rax, esi 147 | movsxd r11, edx 148 | mov r10, r11 149 | sub r10, rax 150 | cmp r10, 7 151 | jbe .LBB0_10 152 | mov r8, r10 153 | and r8, -8 154 | lea rcx, [r8 - 8] 155 | mov rdx, rcx 156 | shr rdx, 3 157 | add rdx, 1 158 | mov r9d, edx 159 | and r9d, 1 160 | test rcx, rcx 161 | je .LBB0_3 162 | sub rdx, r9 163 | lea rcx, [rdi + 4*rax] 164 | add rcx, 48 165 | xor esi, esi 166 | .LBB0_5: # =>This Inner Loop Header: Depth=1 167 | movups xmm0, xmmword ptr [rcx + 4*rsi - 48] 168 | movups xmm1, xmmword ptr [rcx + 4*rsi - 32] 169 | movups xmm2, xmmword ptr [rcx + 4*rsi - 16] 170 | movups xmm3, xmmword ptr [rcx + 4*rsi] 171 | mulps xmm0, xmm0 172 | mulps xmm1, xmm1 173 | movups xmmword ptr [rcx + 4*rsi - 48], xmm0 174 | movups xmmword ptr [rcx + 4*rsi - 32], xmm1 175 | mulps xmm2, xmm2 176 | mulps xmm3, xmm3 177 | movups xmmword ptr [rcx + 4*rsi - 16], xmm2 178 | movups xmmword ptr [rcx + 4*rsi], xmm3 179 | add rsi, 16 180 | add rdx, -2 181 | jne .LBB0_5 182 | test r9, r9 183 | je .LBB0_8 184 | .LBB0_7: 185 | add rsi, rax 186 | movups xmm0, xmmword ptr [rdi + 4*rsi] 187 | movups xmm1, xmmword ptr [rdi + 4*rsi + 16] 188 | mulps xmm0, xmm0 189 | mulps xmm1, xmm1 190 | movups xmmword ptr [rdi + 4*rsi], xmm0 191 | movups xmmword ptr [rdi + 4*rsi + 16], xmm1 192 | .LBB0_8: 193 | cmp r10, r8 194 | je .LBB0_11 195 | add rax, r8 196 | .LBB0_10: # =>This Inner Loop Header: Depth=1 197 | movss xmm0, dword ptr [rdi + 4*rax] # xmm0 = mem[0],zero,zero,zero 198 | mulss xmm0, xmm0 199 | movss dword ptr [rdi + 4*rax], xmm0 200 | add rax, 1 201 | cmp r11, rax 202 | jne .LBB0_10 203 | .LBB0_11: 204 | ret 205 | .LBB0_3: 206 | xor esi, esi 207 | test r9, r9 208 | jne .LBB0_7 209 | jmp .LBB0_8 210 | ---- 211 | 212 | 我们会发现,编译结果和我们的语义有非常大的差异。这是因为编译器识别出了这是一个循环,同时发现了循环内的数据没有依赖性。最后由于数据结构的类型已经被提前定义,数据的宽度可以精确确认。所以编译器就能精确使用 CPU 的 SIMD 指令集来进行运算。于是它就会把多个浮点数数据同时计算,并且会引入一些代码来处理边界情况,从而极大提高运行的性能。 213 | 214 | 这对于 Ruby 灵活的动态类型系统是无法做到的。这就是所谓为性能服务的语法设计。 215 | 216 | ===== 为可读性服务的语法设计 217 | 218 | 在早年流行的高级语言 BASIC 中,一个非常常用的流程控制指令是 `goto`,一个常见的无限循环写法如下(以 1978 年 Apple II Plus 上的 Applesoft BASIC 为例): 219 | 220 | image::applebasic-code.png[Applesoft BASIC Code] 221 | 222 | 运行结果如下: 223 | 224 | image::applebasic-result.png[Applesoft BASIC Code] 225 | 226 | 仔细思考会发现,`goto` 的语义不但可以替代所有循环(`while` `for`),还能替代循环内部的流程控制(`break` `continue`)。`goto` 在现代的 CPU 中都有直接的指令对应实现。在 x86 和 x86-64 系统上就是最直接的 `JMP` 指令,虽然还需要处理一些栈上内存相关的事物,但也可以简单由数个指令执行完毕,性能上是绝对没有问题的。 227 | 228 | 但我们现在很少使用 `goto` 语法的一大原因是因为,`goto` 的功能过于强大,虽然符合机器的执行原理,但是却不符合人的思考直觉。代码不仅仅需要被计算机执行,还需要被人类编写、讨论和维护。可读性的重要性随着工程的复杂度的提高会显得越来越重要。而 `while` `for` `break` `continue` 等一系列限制更严格的条件控制方法的引入更符合了人类的逻辑直觉,从而提高的代码的可读性。 229 | -------------------------------------------------------------------------------- /chapter01/variables.adoc: -------------------------------------------------------------------------------- 1 | === 变量与基本算数 2 | 3 | ==== 整数运算 4 | 5 | 既然计算机叫计算机,那么我们还是从「计算」作为入门的第一步吧。 6 | 7 | 四则运算在 Ruby 语言中的表达几乎和现实世界算数的表达式一样。小小的不同是,由于乘法 `×` 和除法的符号 `÷` 在键盘上不方便输入,于是使用了另两个符号 `*` 和 `/` 来替代乘和除。 8 | 9 | [WARNING] 10 | .特别注意 11 | ==== 12 | 除法符号是 `/` 不是 `\`。 13 | ==== 14 | 15 | [source,ruby] 16 | ---- 17 | 1 + 1 # => 2 18 | 1 - 5 # => -4 19 | 3 * 7 # => 21 20 | 6 / 2 # => 3 21 | ---- 22 | 23 | 需要注意的是,整数之间的加减乘除的结果必然还是整数。如下面的代码所见,整数间的除法运算实际上是整除。 24 | 25 | [source,ruby] 26 | ---- 27 | 5 / 2 # => 2 28 | ---- 29 | 30 | 如果需要获得整除后的余数,Ruby 提供了模除运算 `%`: 31 | 32 | [source,ruby] 33 | ---- 34 | 5 % 2 # => 1 35 | ---- 36 | 37 | 需要特别注意的是,在 Ruby 中对负数取余数的结果可能和其它语言不一样。Ruby 中余数的符号 **永远和除数一致**。 38 | 39 | [source,ruby] 40 | ---- 41 | 5 % 2 # => 1 42 | -5 % 2 # => 1 43 | 44 | 5 % -2 # => -1 45 | -5 % -2 # => -1 46 | ---- 47 | 48 | 这一设计带来了显而易见的好处,在判断一个数字是不是奇数的时候我们只需要 `% 2` 检查结果是否为 `1` 即可。而无需确认其正负。但坏处也是很明显的,如果你尝试从其它编程语言移植程序到 Ruby 上需要格外小心对正负数取模的处理。 49 | 50 | Ruby 的整数没有无穷大和未定形式,如果整数除以零会直接抛出程序错误。如果没有处理错误,在程序运行中会直接退出。 51 | 52 | [source,ruby] 53 | ---- 54 | 3 / 0 # => ZeroDivisionError (divided by 0) 55 | ---- 56 | 57 | Ruby 另一个特色是大整数运算。不同于 C、C++ 语言中整数类型拥有固定的内存分配大小(上限和下限),Ruby 的整数类型的大小是会动态调整的。这使得你可以自由处理非常大的数字而无需考虑溢出问题。 58 | 59 | 例如 C++ 语言中的下面代码: 60 | 61 | [source,c++] 62 | ---- 63 | #include 64 | 65 | int main() { 66 | std::cout << 10000000000000000000 * 10000000000000000000 << std::endl; 67 | return 0; 68 | } 69 | ---- 70 | 71 | 会因为内存溢出,给出一个不符合数学常识的 `687399551400673280` 结果。而在 Ruby 中你完全不用担心这一点。 72 | 73 | [source,ruby] 74 | ---- 75 | 10000000000000000000 * 10000000000000000000 # => 100000000000000000000000000000000000000 76 | ---- 77 | 78 | 这一特性在幂运算时特别实用。Ruby 的幂运算符号是 `+**+`。`+a**b+` 相当于 latexmath:[a^b]。 79 | 80 | [source,ruby] 81 | ---- 82 | 2**256 # => 115792089237316195423570985008687907853269984665640564039457584007913129639936 83 | ---- 84 | 85 | ==== 局部变量 86 | 87 | 变量 (variable) 是程序开发的基础概念之一。变量是一种对临时存储的抽象概念。Ruby 中的变量有 4 种类型: 88 | 89 | - 局部变量 `variable` 90 | - 实例变量 `@variable` 91 | - 类变量 `@@variable` 92 | - 全局变量 `$variable` 93 | 94 | 这里所介绍的变量是局部变量,至于其它变量的时候,我们会在之后章节中再做介绍。 95 | 96 | Ruby 的局部变量以小写字母或下划线 `_` 开头。Ruby 中的 `=` 符号和数学中的等于不同,表示「赋值」符号。表示将 `=` 符号 **右边** 的结果保存到 **左边** 的变量里。数学中等号两边内容可以互换,但在赋值中是 **绝对** 不能的。 97 | 98 | 在 irb 中试一下下面的代码,来理解局部变量和 `=` 的使用。 99 | 100 | [source,ruby] 101 | ---- 102 | a = 1 103 | a + 1 # => 2 104 | a + 3 # => 4 105 | a = 2 106 | a + 1 # => 3 107 | a + 3 # => 5 108 | 2 = a # SyntaxError 109 | ---- 110 | 111 | ===== 试一试 112 | 113 | - 创建两个变量 `a` 和 `b`,给它们赋两个整数,计算并打印它们的和。 114 | - 创建两个变量 `a` 和 `b`,给它们赋两个整数,计算并打印它们的积。 115 | - 创建三个变量 `a` 和 `b` 和 `c`,给 `a` 和 `b` 赋两个整数,计算它们的和,将它们的结果写入 `c` 变量中。 116 | 117 | **答案见《附录三:参考答案》中的 局部变量 小节** 118 | 119 | ==== Ruby 的类型系统 120 | 121 | 类型系统自古以来都是高级语言的一个非常重要的属性。所谓类型,指的是程序语言中变量、常量的数据类型,类型是对数据的约束。比如我们可以检查这一变量是数字还是字符串,从而避免超出我们预料的行为。 122 | 123 | 基于不同的分类方法可以把不同语言的类型系统分成不同类别。一般我们会说 Ruby 的类型系统是一个动态类型、强类型系统。其中动态类型与静态类型相对,而强类型与弱类型相对。类型的动态与静态、强与弱是一对正交(orthogonal)的概念。 124 | 125 | image::type-system-example.png[Type System Example] 126 | 127 | 理解这一问题,我们先要理解类型可能的错误,以及错误带来的问题。 128 | 129 | 一类问题是出现在类似于 C 语言中的内存管理带来的错误。比如说根据某一变量的类型分配了一定空间,但是在写入的时候,占用了大于既定的空间。C 语言不会检查这一类的错误,从而造成程序写入到了其它变量 / 程序本来占用着的空间,进而产生完全未知的行为。这一问题称为「缓冲区溢出」,这会带来未知的程序错误,同时还有可能被黑客巧妙利用从而进一步攻击你的程序。一个典型的案例是 SONY 在 PSP-3000 游戏机上的相册应用存在一个「缓冲区溢出」漏洞,通过一张特殊的图片,黑客实现了对 PSP-3000 的破解,从而运行黑客指定的第三方程序。 130 | 131 | 另一类问题则是由隐式类型转换带来的问题。这一类问题的翘楚就是 PHP 和 JavaScript。这两门语言在运算所需类型不满足的情况下,比起抛出错误,更愿意尝试一系列自动的类型转换来满足需求,而这一过程会带来一系列的问题。 132 | 133 | 比如在 PHP 中著名的 Type Juggling 漏洞,使用 `==` 符号比较两个完全不同的字符串: 134 | 135 | [source,php] 136 | ---- 137 | if ('0e1234' == '0e5678') { 138 | echo('wat'); 139 | } 140 | ---- 141 | 142 | 程序会返回 `wat`,纵使字符串 `0e1234` 和 `0e5678` 完全不相同,但是 PHP 发现了这些字符串都是 `0e` 开始的,于是会认为这 **可能** 是 https://zh.wikipedia.org/wiki/%E7%A7%91%E5%AD%A6%E8%AE%B0%E6%95%B0%E6%B3%95[科学记数法] 的数字。而无论 0 的多少次方都是 0,所以两者都是 0,因此认为这两个字符串相等。这显然不是预期行为。这一方法常会在一些特殊情况引发更严重的问题,比如检查用户登录密码时,一般数据库存储哈希运算后的 16 进制密码以防止明文泄漏。而 16 进制数有 latexmath:[\frac{1}{256}] 的概率以 `0e` 开头,同时以 `00e`、`000e` 开头的同样也会被以同样的方式处理,因此给定 `N` 位长的字符串,PHP 会有 latexmath:[\displaystyle\sum_{i=2}^n \frac{1}{16^n}] 的概率认为其等于 0。 143 | 144 | 145 | 这两类由类型带来的问题是高级语言需要尽力避免的。 146 | 147 | 在运行过程中不可能出现类型错误问题的,是强类型系统;而如果这门语言在运行过程中会因为类型错误而产生不可知的任意行为的,那么就是弱类型系统。 148 | 149 | 在编译期(运行程序前)检查类型错误的是静态类型系统,在运行过程中拒绝类型错误的程序继续运行的,则是动态类型系统。 150 | 151 | 虽然 Ruby 考虑在未来的 3.0 版本中引入静态类型检查系统,目前的 Ruby 2.7 仍是运行时的动态类型检查。但是 Ruby 是强类型语言,会在运行通过抛出类型错误,来避免错误的类型转换带来潜在的风险。 152 | -------------------------------------------------------------------------------- /chapter02/math.adoc: -------------------------------------------------------------------------------- 1 | === 基础数学运算 2 | 3 | ==== 整数以外的运算 4 | 5 | 在第一周的《变量与基本算数》中我们已经学习了整数的运算,在这一章节,我们会学习其它的 Ruby 数学运算。 6 | 7 | ===== 浮点数运算 8 | 9 | Ruby 中的小数默认是根据 IEEE 754 规范定义的双精度浮点数。 10 | 11 | [source,ruby] 12 | ---- 13 | 0.1 + 0.2 # => 0.30000000000000004 14 | ---- 15 | 16 | ===== 高精度小数运算 17 | 18 | [source,ruby] 19 | ---- 20 | require 'bigdecimal' 21 | 22 | BigDecimal('0.1') + BigDecimal('0.2') # => 0.3e0 23 | ---- 24 | 25 | ===== 布尔(逻辑)运算 26 | 27 | 比起整数和浮点数运算,布尔运算可能是最简单的,但却是大家日常最不熟悉的。 28 | 29 | 布尔运算就是关于「是」「非」的计算,在 Ruby 中这两者的关键字是 `true` 和 `false`。布尔运算的三个最基本运算是「与(and)」、「或(or)」、「非(not)」。 30 | 31 | - 与运算(`and`)就是当算符两侧皆为 `true` 那么结果还是 `true`,否则为 `false`。 32 | - 或运算(`or`)当算符两侧只要有一个操作数为 `true` 那么结果就为 `true`。 33 | - 非运算(`not`)只接受一个操作数,将 `true` 变成 `false`,将 `false` 变成 `true`。 34 | 35 | 用 Ruby 代码写如下,大家可以打开 irb 试一下: 36 | 37 | [source,ruby] 38 | ---- 39 | 40 | true and true # => true 41 | true and false # => false 42 | false and true # => false 43 | false and false # => false 44 | 45 | true or true # => true 46 | true or false # => true 47 | false or true # => true 48 | false or false # => false 49 | 50 | not true # => false 51 | not false # => true 52 | ---- 53 | 54 | Ruby 中的 `and` `or` `not` 也可以由符号 `&&` `||` `!` 来替代。 55 | 56 | [source,ruby] 57 | ---- 58 | true && true # => true 59 | true && false # => false 60 | false && true # => false 61 | false && false # => false 62 | 63 | true || true # => true 64 | true || false # => true 65 | false || true # => true 66 | false || false # => false 67 | 68 | !true # => false 69 | !false # => true 70 | ---- 71 | 72 | [WARNING] 73 | .特别注意 74 | ==== 75 | 是 `&&` 和 `||` 而不是 `&` 和 `|`。`&` 和 `|` 这两个是二进制运算符,和布尔运算有一定差异,我们会在之后具体介绍这两个运算。 76 | ==== 77 | 78 | 逻辑运算也可以使用括号进行优先级的组合,如果没有括号,默认的计算优先级是 `not` 优先于 `and` 优先于 `or`。一个好记的优先级口诀是「not at all. (not and or)」。 79 | 80 | 尝试猜测下面这句代码的结果,打开 irb 试一试,检验自己的理解是否正确。 81 | 82 | [source,ruby] 83 | ---- 84 | true && (true and not false) or !false || false 85 | ---- 86 | 87 | ==== Ruby 标准数学库 88 | -------------------------------------------------------------------------------- /chapter09/is_eval_evil.md: -------------------------------------------------------------------------------- 1 | # Is eval evil? 2 | 3 | > 这篇文章取自博客,需要大幅改写 4 | 5 | ## 引 6 | 7 | 对于大多数动态语言,都支持 `eval` 这个神奇的函数。这打他们太爷爷 Lisp 开始就支持这种方法。虽然写法(eg: `(eval '(+ 1 2 3))` )有稍许不同,但语义是一样的,就是说 `eval` 函数接受一个字符串类型作为参数,将其解析成语句并混合在当前作用域内运行。但我想大家也都听过这么一句话: 8 | 9 | > eval is evil. 10 | 11 | 但是 How evil is eval?那么既然 `eval` 如此罪恶,那么为什么它仍被那么多动态语言作为接口暴露呢?我们不妨来仔细探讨一下 `eval` 在使用中究竟会产生什么问题,在日常编程中究竟该不该使用 `eval`,如果要用,那么又该如何使用。 12 | 13 | ## 字符串安全 14 | 15 | 说到 `eval` 第一个会被讨论的当然就是其安全性。比如说,我们现在来实现一个四则运算器: 16 | 17 | ```ruby 18 | loop do 19 | print('Expression: ') 20 | puts("result: #{eval gets.chomp}") 21 | end 22 | ``` 23 | 24 | ```text 25 | Expression: 1+1 26 | result: 2 27 | Expression: 2+2 28 | result: 4 29 | Expression: 3*4 30 | result: 12 31 | Expression: 4+(1*1) 32 | result: 6 33 | ``` 34 | 35 | Awesome! 这段程序实现了全部四则运算的功能!只不过,这东西实现了 **不止** 四则运算的功能,事实上,它能处理任意 Ruby 语句,实际上这已经是一个 REPL(Read-Eval-Print Loop)了。我们可以运行一些「危险」的代码,比如: 36 | 37 | ```text 38 | Expression: exit 39 | Process exit with code 0 40 | ``` 41 | 42 | 更实际的应用是,在 JavaScript 中,如果你想要支持 IE7 的话,`JSON.parse()` 是不被支持的,除非你引入一个 JSON 解析库,最方便的写法就是 `eval(json)`,因为毕竟 JSON 也是合法的 JavaScript 语句,这样的方法安全性问题是显然的。 43 | 44 | 这样的问题不止 `eval` 有,事实上,所有和字符串打交道的事情,或多或少都有类似的问题。比如「SQL 注入」说到底也就是这么一回事。再比如你在 Ruby 代码中试图操作 `git` 命令的时候,如果你使用反引号,也可能遇到这样的问题。 45 | 46 | 不过我们来看下面这个例子: 47 | 48 | ```ruby 49 | # gems/rest-client-1.6.7/bin/restclient 摘自《Ruby 元编程(第二版)》第 142 页 50 | POSSIBLE_VERBS = ['get', 'put', 'post', 'delete'] 51 | POSSIBLE_VERBS.each do |m| 52 | eval <<-end_eval 53 | def #{m}(path, *args, &b) 54 | r[path].#{m}(*args, &b) 55 | end 56 | end_eval 57 | end 58 | ``` 59 | 60 | 在这个例子中,也使用了 `eval`,也在 `eval` 中拼接了字符串,存在字符串拼接的安全性问题吗?并没有。因为是从常量数组中读取的字符串,并不存在用户任意输入导致注入的问题。 61 | 62 | 但,我们反过来说,SQL 驱动自带的 `query` 函数都可能存在注入,难道我们就不用 SQL 了吗?并没有。事实上,注入问题是可以被解决的。如果我们做好对用户输入的 **过滤** 和 **转义** 同样也能解决。问题就在于,这么做的成本和 `eval` 带来的动态性好处,哪个更大的权衡问题。 63 | 64 | ## Lexer & Parser 65 | 66 | > 本来想用中文写这个小标题,但感觉「词法分析器和语法分析器」实在这标题太长了 67 | 68 | 学过一些《编译原理》都知道,无论是解释器还是编译器,拿到字符串无非就是 69 | 70 | `词法分析 -> 语法分析 -> 语义检查 -> 生成语法树 -> 代码优化 -> 生成目标代码/执行` 71 | 72 | 使用 `eval` 函数意味着,你的程序需要在运行时经历全部的这些步骤。通常来说即使是脚本语言,在你加载所有文件初始化运行的过程中。前期步骤通常都已经完成了,只剩下最后一步执行了。不过加入 `eval` 之后就不一样了。因为 `eval` 传入的是字符串,所以这意味着它需要对这一部分代码从词法分析开始重新走一遍。事实上,词法分析、语法分析、语义检查并不快。 73 | 74 | 比如说,我们对比下面的代码 75 | 76 | ```ruby 77 | GC.disable # 禁用 GC 以避免后一段代码在运行过程中遭遇 GC 对其不公 78 | 79 | time = Time.now.to_f 80 | 1000000.times do 81 | rand+rand 82 | end 83 | puts "Without eval: #{Time.now.to_f - time}" 84 | 85 | time = Time.now.to_f 86 | 1000000.times do 87 | eval('rand+rand') 88 | end 89 | puts "With eval: #{Time.now.to_f - time}" 90 | ``` 91 | 92 | ```text 93 | Without eval: 0.11499691009521484 94 | With eval: 6.516125917434692 95 | ``` 96 | 97 | 慢了 55 倍。如果我们用 Ruby 自带的 profiler 对这两段代码跑一下的话,结果如下: 98 | 99 | 没有 eval: 100 | 101 | ```text 102 | % cumulative self self total 103 | time seconds seconds calls ms/call ms/call name 104 | 62.15 17.03 17.03 1000000 0.02 0.02 nil# 105 | 18.65 22.14 5.11 1 5110.00 27400.00 Integer#times 106 | 12.77 25.64 3.50 2000000 0.00 0.00 Kernel#rand 107 | 6.42 27.40 1.76 1000000 0.00 0.00 Float#+ 108 | 0.00 27.40 0.00 2 0.00 0.00 Time.now 109 | 0.00 27.40 0.00 2 0.00 0.00 Fixnum#fdiv 110 | 0.00 27.40 0.00 2 0.00 0.00 Numeric#quo 111 | 0.00 27.40 0.00 2 0.00 0.00 Time#to_f 112 | 0.00 27.40 0.00 1 0.00 0.00 Float#- 113 | 0.00 27.40 0.00 1 0.00 0.00 Float#to_s 114 | 0.00 27.40 0.00 2 0.00 0.00 IO#write 115 | 0.00 27.40 0.00 1 0.00 0.00 IO#puts 116 | 0.00 27.40 0.00 1 0.00 0.00 Kernel#puts 117 | 0.00 27.40 0.00 1 0.00 0.00 TracePoint#enable 118 | 0.00 27.40 0.00 1 0.00 0.00 TracePoint#disable 119 | 0.00 27.40 0.00 2 0.00 0.00 IO#set_encoding 120 | 0.00 27.40 0.00 2 0.00 0.00 Fixnum#+ 121 | 0.00 27.40 0.00 2 0.00 0.00 Time#initialize 122 | 0.00 27.40 0.00 1 0.00 27400.00 #toplevel 123 | ``` 124 | 125 | 有 eval: 126 | 127 | ```text 128 | % cumulative self self total 129 | time seconds seconds calls ms/call ms/call name 130 | 57.58 25.67 25.67 1000000 0.03 0.03 Kernel#eval 131 | 16.02 32.81 7.14 1000000 0.01 0.04 nil# 132 | 13.14 38.67 5.86 1 5860.00 44580.00 Integer#times 133 | 9.17 42.76 4.09 2000000 0.00 0.00 Kernel#rand 134 | 4.08 44.58 1.82 1000000 0.00 0.00 Float#+ 135 | 0.00 44.58 0.00 2 0.00 0.00 Fixnum#fdiv 136 | 0.00 44.58 0.00 2 0.00 0.00 Numeric#quo 137 | 0.00 44.58 0.00 2 0.00 0.00 Time#to_f 138 | 0.00 44.58 0.00 1 0.00 0.00 Float#- 139 | 0.00 44.58 0.00 1 0.00 0.00 Float#to_s 140 | 0.00 44.58 0.00 2 0.00 0.00 IO#write 141 | 0.00 44.58 0.00 1 0.00 0.00 IO#puts 142 | 0.00 44.58 0.00 1 0.00 0.00 Kernel#puts 143 | 0.00 44.58 0.00 1 0.00 0.00 TracePoint#enable 144 | 0.00 44.58 0.00 1 0.00 0.00 TracePoint#disable 145 | 0.00 44.58 0.00 2 0.00 0.00 IO#set_encoding 146 | 0.00 44.58 0.00 2 0.00 0.00 Fixnum#+ 147 | 0.00 44.58 0.00 2 0.00 0.00 Time#initialize 148 | 0.00 44.58 0.00 2 0.00 0.00 Time.now 149 | 0.00 44.58 0.00 1 0.00 44580.00 #toplevel 150 | ``` 151 | 152 | 虽然在 profile 的掺和下,差距被缩小到了几倍之内,但也可以看出 `eval` 函数自行的语法解析有多么耗时。所以说,如果你的 `eval` 是一次性运行与加载时候的,比如上面那个 Rest Client 的例子里,问题并不大,但如果你的 `eval` 是被频繁调用的话,使用 `eval` 是非常影响性能的,不应该这么使用。 153 | 154 | ## 静态分析与代码优化 155 | 156 | 在分析这个问题前,我们先来看两段代码: 157 | 158 | ```cpp 159 | #include 160 | void recursion_loop(int count){ 161 | printf("Count: %d\n", count); 162 | if (count == 0) {return;} 163 | recursion_loop(count - 1); 164 | } 165 | 166 | int main(){ 167 | recursion_loop(100000); 168 | return 0; 169 | } 170 | ``` 171 | 172 | ```ruby 173 | def recursion_loop(count) 174 | puts("Count: #{count}") 175 | return if count == 0 176 | recursion_loop(count - 1) 177 | end 178 | 179 | recursion_loop(100000) 180 | ``` 181 | 182 | 为什么第一段代码在 {cpp} 中可以正确运行(注:需要开启 -O2 编译选项),而第二段代码在 Ruby 下会报错。 183 | 184 | ```text 185 | /Users/Delton/RubymineProjects/untitled/script4.rb:2:in `puts': stack level too deep (SystemStackError) 186 | from /Users/Delton/RubymineProjects/untitled/script4.rb:2:in `puts' 187 | from /Users/Delton/RubymineProjects/untitled/script4.rb:2:in `recursion_loop' 188 | from /Users/Delton/RubymineProjects/untitled/script4.rb:4:in `recursion_loop' 189 | from /Users/Delton/RubymineProjects/untitled/script4.rb:4:in `recursion_loop' 190 | from /Users/Delton/RubymineProjects/untitled/script4.rb:4:in `recursion_loop' 191 | from /Users/Delton/RubymineProjects/untitled/script4.rb:4:in `recursion_loop' 192 | from /Users/Delton/RubymineProjects/untitled/script4.rb:4:in `recursion_loop' 193 | from /Users/Delton/RubymineProjects/untitled/script4.rb:4:in `recursion_loop' 194 | ... 10908 levels... 195 | from /Users/Delton/RubymineProjects/untitled/script4.rb:4:in `recursion_loop' 196 | from /Users/Delton/RubymineProjects/untitled/script4.rb:7:in `' 197 | from -e:1:in `load' 198 | from -e:1:in `
' 199 | ``` 200 | 201 | 报错的原因很显然,栈太深了。这个用递归实现的循环,需要建一个深度高达 100000 层深栈。一般的运行时都不会允许这么深的栈。诶?等一下,那为什么在 C++ 中这段代码可以正常运行呢?因为你的 C++ 编译器发现了这是一个「尾递归」,可以进行「尾递归优化」。尾递归可以被优化成一个非递归形式,自然就不需要那么深的栈了。 202 | 203 | 这就是 `词法分析 -> 语法分析 -> 语义检查 -> 生成语法树 -> 代码优化 -> 生成目标代码/执行` 的倒数第二步。Ruby 中也有尾递归优化的选项,但默认不开启。开启的话方法也比较复杂,需要用到 `InstrctionSequence` 这个编译中间码的类,代码如下: 204 | 205 | ```ruby 206 | RubyVM::InstructionSequence.compile_option = { 207 | :tailcall_optimization => true, 208 | :trace_instruction => false 209 | } 210 | 211 | source = <<-end_source 212 | def recursion_loop(count) 213 | puts("Count: \#{count}") 214 | return if count == 0 215 | recursion_loop(count - 1) 216 | end 217 | recursion_loop(100000) 218 | end_source 219 | 220 | RubyVM::InstructionSequence.new(source).eval 221 | ``` 222 | 223 | **注:事实上,这里开启的是尾调用优化,尾调用是尾递归的超集,开启尾调用优化不止会优化尾递归。** 224 | 225 | 但下面这段尾递归代码在即使开启优化的情况下,一样不会得到优化: 226 | 227 | ```ruby 228 | def recursion_loop(count) 229 | puts("Count: #{count}") 230 | return if count == 0 231 | eval('recursion_loop(count - 1)') 232 | end 233 | 234 | recursion_loop(100000) 235 | ``` 236 | 237 | 这段代码把一个明明是尾递归的情况破坏成了非尾递归。因为编译器静态分析的时候根本不知道你 `eval` 里是什么东西。怎么可能给你优化? 238 | 239 | 虽然这个例子非常极端,但其实不止 `eval`,比如: 240 | 241 | ```ruby 242 | @next_recursion = proc { |count| 243 | recursion_loop(count - 1) 244 | } 245 | 246 | def recursion_loop(count) 247 | puts("Count: #{count}") 248 | return if count == 0 249 | @next_recursion.call(count) 250 | end 251 | 252 | recursion_loop(100000) 253 | ``` 254 | 255 | 大多数动态方法,都没有办法被静态分析,以提供足够的优化。以至于动态语言的代码优化也一直是一大难点。 256 | 257 | ## 对 eval 魔法的思考 258 | 259 | 总结一下,谈一谈对「eval 魔法」的思考。`eval` 在元编程里也一直属于接近于禁术的那种类型。就好像核武器一样可怕。在用 `eval` 的时候要充分认识到可能带来的后果,才能对其进行使用。核国家的战争也不一定非要互相丢核武器,如果常规武器可以解决的,不必如此大动干戈。比如说前面 rest-client 的例子,也可以不用 `eval`: 260 | 261 | ```ruby 262 | POSSIBLE_VERBS = ['get', 'put', 'post', 'delete'] 263 | POSSIBLE_VERBS.each do |m| 264 | define_method(m) do |path, *args, &b| 265 | r[path].send(m, *args, &b) 266 | end 267 | end 268 | ``` 269 | 270 | 也可以解决这样的问题。不过 `define_method` 的方法,也不能给代码带来静态分析,而这又是在启动时一次性执行的代码,对性能的提升是微乎其微的。所以 rest-client 的 `eval` 实现并谈不上 evil。 271 | 272 | `eval` 被那么多语言至今沿用,其巨大的灵活性带来的便利是毋庸置疑的。只是说,使用 `eval` 包括使用任何元编程技巧的时候,都要充分考虑到这么做的可能造成的后果,以免莽撞瞎写,误伤自己。 273 | 274 | ## 彩蛋 275 | 276 | 在 [Leetcode #20](https://leetcode.com/problems/valid-parentheses/) 括号匹配问题里,有一个可以用 JavaScript 的 `eval` 实现的魔法写法,非常有趣,大家可以看看开心开心。 277 | 278 | ```javascript 279 | /** 280 | * @param {string} s 281 | * @return {boolean} 282 | */ 283 | var isValid = function(s) { 284 | s = s.replace(/\(/g, '+('); 285 | s = s.replace(/\[/g, '+['); 286 | s = s.replace(/\{/g, '+{0:'); 287 | s = s.replace(/\)/g, '+0)'); 288 | s = s.replace(/\]/g, '+0]'); 289 | s = s.replace(/\}/g, '+0}'); 290 | try{ 291 | eval(s); 292 | } catch(err) { 293 | return false; 294 | } 295 | return true; 296 | }; 297 | ``` 298 | -------------------------------------------------------------------------------- /cjk-gothic.rb: -------------------------------------------------------------------------------- 1 | module Asciidoctor 2 | module Pdf 3 | module CJK 4 | module CJKGothic 5 | VERSION = "0.0.1" 6 | end 7 | 8 | def self.break_words(string) 9 | string.gsub(/(?\u203a " 92 | heading: 93 | align: left 94 | #font_color: 181818 95 | font_color: $base_font_color 96 | font_family: $base_font_family 97 | font_style: bold 98 | # h1 is used for part titles (book doctype) or the doctitle (article doctype) 99 | h1_font_size: floor($base_font_size * 2.6) 100 | # h2 is used for chapter titles (book doctype only) 101 | h2_font_size: floor($base_font_size * 2.15) 102 | h3_font_size: round($base_font_size * 1.7) 103 | h4_font_size: $base_font_size_large 104 | h5_font_size: $base_font_size 105 | h6_font_size: $base_font_size_small 106 | line_height: 1.5 107 | margin_top: $vertical_rhythm * 0.4 108 | margin_bottom: $vertical_rhythm * 0.9 109 | min_height_after: $base_line_height_length * 1.5 110 | title_page: 111 | align: right 112 | logo: 113 | top: 10% 114 | title: 115 | top: 55% 116 | font_size: $heading_h1_font_size 117 | font_color: 999999 118 | line_height: 0.9 119 | subtitle: 120 | font_size: $heading_h3_font_size 121 | font_style: bold 122 | line_height: 1 123 | authors: 124 | margin_top: $base_font_size * 1.25 125 | font_size: $base_font_size_large 126 | font_color: 181818 127 | revision: 128 | margin_top: $base_font_size * 1.25 129 | block: 130 | margin_top: 0 131 | margin_bottom: $vertical_rhythm 132 | caption: 133 | align: left 134 | font_size: $base_font_size * 0.95 135 | font_style: bold 136 | # FIXME perhaps set line_height instead of / in addition to margins? 137 | margin_inside: $vertical_rhythm / 3 138 | #margin_inside: $vertical_rhythm / 4 139 | margin_outside: 0 140 | lead: 141 | font_size: $base_font_size_large 142 | line_height: 1.5 143 | abstract: 144 | font_color: 5c6266 145 | font_size: $lead_font_size 146 | line_height: $lead_line_height 147 | font_style: bold 148 | first_line_font_style: bold 149 | title: 150 | align: center 151 | font_color: $heading_font_color 152 | font_family: $heading_font_family 153 | font_size: $heading_h4_font_size 154 | font_style: $heading_font_style 155 | admonition: 156 | column_rule_color: $base_border_color 157 | column_rule_width: $base_border_width 158 | padding: [0, $horizontal_rhythm, 0, $horizontal_rhythm] 159 | font_color: 999999 160 | icon: 161 | tip: 162 | name: far-lightbulb 163 | stroke_color: 111111 164 | size: 24 165 | label: 166 | text_transform: uppercase 167 | font_style: bold 168 | blockquote: 169 | font_size: $base_font_size_large 170 | border_color: $base_border_color 171 | border_width: 0 172 | border_left_width: 5 173 | # FIXME disable negative padding bottom once margin collapsing is implemented 174 | padding: [0, $horizontal_rhythm, $block_margin_bottom * -0.75, $horizontal_rhythm + $blockquote_border_left_width / 2] 175 | cite_font_size: $base_font_size_small 176 | cite_font_color: 999999 177 | verse: 178 | font_size: $blockquote_font_size 179 | border_color: $blockquote_border_color 180 | border_width: $blockquote_border_width 181 | border_left_width: $blockquote_border_left_width 182 | padding: $blockquote_padding 183 | cite_font_size: $blockquote_cite_font_size 184 | cite_font_color: $blockquote_cite_font_color 185 | # code is used for source blocks (perhaps change to source or listing?) 186 | code: 187 | font_color: $base_font_color 188 | font_family: $literal_font_family 189 | font_size: ceil($base_font_size) 190 | padding: $code_font_size 191 | line_height: 1.25 192 | # line_gap is an experimental property to control how a background color is applied to an inline block element 193 | line_gap: 3.8 194 | background_color: f5f5f5 195 | border_color: cccccc 196 | border_radius: $base_border_radius 197 | border_width: 0.75 198 | conum: 199 | font_family: $literal_font_family 200 | font_color: $literal_font_color 201 | font_size: $base_font_size 202 | line_height: 4 / 3 203 | glyphs: circled 204 | example: 205 | border_color: $base_border_color 206 | border_radius: $base_border_radius 207 | border_width: 0.75 208 | background_color: ffffff 209 | # FIXME reenable padding bottom once margin collapsing is implemented 210 | padding: [$vertical_rhythm, $horizontal_rhythm, 0, $horizontal_rhythm] 211 | image: 212 | align: left 213 | prose: 214 | margin_top: $block_margin_top 215 | margin_bottom: $block_margin_bottom 216 | sidebar: 217 | background_color: eeeeee 218 | border_color: e1e1e1 219 | border_radius: $base_border_radius 220 | border_width: $base_border_width 221 | # FIXME reenable padding bottom once margin collapsing is implemented 222 | padding: [$vertical_rhythm, $vertical_rhythm * 1.25, 0, $vertical_rhythm * 1.25] 223 | title: 224 | align: center 225 | font_color: $heading_font_color 226 | font_family: $heading_font_family 227 | font_size: $heading_h4_font_size 228 | font_style: $heading_font_style 229 | thematic_break: 230 | border_color: $base_border_color 231 | border_style: solid 232 | border_width: $base_border_width 233 | margin_top: $vertical_rhythm * 0.5 234 | margin_bottom: $vertical_rhythm * 1.5 235 | description_list: 236 | term_font_style: bold 237 | term_spacing: $vertical_rhythm / 4 238 | description_indent: $horizontal_rhythm * 1.25 239 | outline_list: 240 | indent: $horizontal_rhythm * 1.5 241 | #marker_font_color: 404040 242 | # NOTE outline_list_item_spacing applies to list items that do not have complex content 243 | item_spacing: $vertical_rhythm / 2 244 | table: 245 | background_color: $page_background_color 246 | border_color: dddddd 247 | border_width: $base_border_width 248 | cell_padding: 3 249 | head: 250 | font_style: bold 251 | border_bottom_width: $base_border_width * 2.5 252 | body: 253 | stripe_background_color: f9f9f9 254 | foot: 255 | background_color: f0f0f0 256 | toc: 257 | indent: $horizontal_rhythm 258 | line_height: 1.5 259 | dot_leader: 260 | #content: ". " 261 | font_color: a9a9a9 262 | #levels: 2 3 263 | footnotes: 264 | font_size: round($base_font_size * 0.75) 265 | item_spacing: $outline_list_item_spacing / 2 266 | header: 267 | font_size: $base_font_size_small 268 | line_height: 1 269 | vertical_align: middle 270 | footer: 271 | font_size: $base_font_size_small 272 | # NOTE if background_color is set, background and border will span width of page 273 | border_color: dddddd 274 | border_width: 0.25 275 | height: $base_line_height_length * 2.5 276 | line_height: 1 277 | padding: [$base_line_height_length / 2, 1, 0, 1] 278 | vertical_align: top 279 | recto: 280 | #columns: "<50% =0% >50%" 281 | right: 282 | content: '{page-number}' 283 | verso: 284 | #columns: $footer_recto_columns 285 | left: 286 | content: $footer_recto_right_content 287 | -------------------------------------------------------------------------------- /images/applebasic-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/applebasic-code.png -------------------------------------------------------------------------------- /images/applebasic-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/applebasic-result.png -------------------------------------------------------------------------------- /images/cake-recipe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/cake-recipe.jpg -------------------------------------------------------------------------------- /images/font-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/font-preview.png -------------------------------------------------------------------------------- /images/punch-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/punch-card.png -------------------------------------------------------------------------------- /images/stem-415290769594460e2e485922904f345d.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /images/stem-44336aba7a4c4db9a219816fe9baa42b.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /images/stem-8170edd3c0342bbe5d6e254fda14cd67.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /images/stem-8fa14cdd754f91cc6554c9e71929cce7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /images/stem-9dd4e461268c8034f5c8564e155c67a6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /images/stem-bbd87e394c9a2e7a45fd63ed40ba0722.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /images/stem-fd91c508f91c2c84498680bd337c1d7a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /images/type-system-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/type-system-example.png -------------------------------------------------------------------------------- /images/vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/vscode.png -------------------------------------------------------------------------------- /images/xcode-autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsh0416/ruby-relearning/7b7791cd2b5191a3dda181235137e6ffc72948cc/images/xcode-autocomplete.png -------------------------------------------------------------------------------- /preface/README.adoc: -------------------------------------------------------------------------------- 1 | == 写在一切之前 2 | 3 | === 为什么要写这本书? 4 | 5 | 市面上关于编程入门的书多到无法想象,但是其中令人满意的却很少。事实上,写一本面向初学者的书比写一本面向专家的书更困难。面向专家的书籍,最重要的是专业性和正确性,而面向初学者的书籍在此基础上还要加上通俗性。市面上的一些书籍,在通俗性和正确性上往往二者不能兼顾。 6 | 7 | 对于初学者来说,给予错误资讯的危险性是极大的。如果你今天拿到一份乐谱准备练琴,练了一个月发现,调号就没看对。等你再去纠正,形成了肌肉记忆的手再想纠正回来就必须付出更多的代价。我虽然对于 Ruby 编程也算颇有经验,但也绝不能说内容 100% 的正确性。我也不敢像 Donald E. Knuth 老先生那样为第一个发现错误的准备 2.56 美金,此后每发现一个翻倍,这样我离破产基本上是不远的。维护这本书正确性的核心,一方面是反复校稿,其次是维护了一套 CI 系统来自动化测试书中代码行为是否和描述一致,但更重要的就是秉持开放的态度,基于 GitHub 进行协作。欢迎大家自由为书籍纠错、 提意见。 8 | 9 | 至于通俗性,我觉得大家要有对编程难度的一个正确认知。如果你觉得编程对你很难,这里的「难」指的是「问题困难」还是「问题复杂」。「问题困难」往往是一些计算机科学学科强相关的内容,比如算法、数据结构、形式语言;而「问题复杂」常常是工程问题,里面的内容通常是你所熟知的,唯一的问题是你没有办法很好解构这个问题,将复杂的实际问题转换成一系列你熟悉的小问题。 10 | 11 | 我非常喜欢陶哲轩先生所写的《实分析》,因为他把一个「困难」的数学分析问题讲得很简单易懂,和他比起来我还差太远。好在这本书所涉及的问题,几乎都是后一类问题,很少会涉及前一类问题。所以大可不必妄自菲薄,你一定具备理解这本书内容的前置知识。同时,这本书会在每个章节后通过一系列复杂性逐渐上升的综合练习,让你可以慢慢解构问题,最终能够独当一面完成一个复杂的项目。 12 | 13 | === 什么人适合读这本书? 14 | 15 | - 想要了解编程的人 16 | - 想要将编程作为自己工作而入门的人 17 | - 想要学习 Ruby 编程语言的人 18 | - 有一定编程基础,想尝试 Ruby 语言的人 19 | - 有一定 Ruby 基础,想进一步理解 Ruby 的人 20 | 21 | === 什么是 Ruby? 22 | 23 | Ruby 是最初由 Matz(Yukihiro Matsumoto)于 1993 年开发,现在作为开源软件开发的语言。它可以在多个平台上运行,并在世界各地使用。尤其适合于网站的开发。 24 | 25 | === 为什么选择 Ruby? 26 | 27 | 因为我喜欢 Ruby。 28 | 29 | Ruby 是一门适合用来作为入门教学的语言。使用这门语言不需要对计算机组成原理有着比较深刻的理解。使用这门语言进行教学,我们不但可以学习形如面向对象等在工程中常用的范式,还可以掌握形如 Lambda 演算、元编程这样非常 Lisp 的特性。站在功利的角度来说,Ruby 不是一门非常大众的语言,虽然可能岗位相比 Java、PHP 少很多,但是相对的竞争的求职者也更少。但是,编程的工具绝不是一招鲜吃遍天的,10 年前所流行的框架放到今天可能多半已经被淘汰了。如果单靠一门技术就想在这个行业活到退休是非常困难的,但是这些框架背后的思想确是共通的。 30 | 31 | Ruby 的创始人 Matz 曾经说过:「Ruby 的首要设计目标是让这个世界上每一位程序员都变得充满生产力,享受编程,以及变得开心。」无论你最后会不会选择 Ruby 作为工作,都不妨碍我们用 Ruby 作为教学语言。 32 | 33 | === 怎么阅读这本书? 34 | 35 | 我把这个书的设计理念定义为「在两座山脊之间寻找山谷」。这两座山脊分别是站在语言学习者角度语法使用的山脊,以及站在语言设计和实现角度对语言特性思考的山脊。 36 | 37 | 所谓「学而不思则罔,思而不学则殆」。如果只学习基本的语法使用,没有认清很多设计面向的场景,没有理解语言的思考方式,不但写出来的代码非常脏乱差,而且实际使用的时候往往不知从何下手。 38 | 39 | 本书按知识点的逻辑关系进行排序,因此难度上可能存在跳跃。在一些章节,本书会使用如下的提示来提醒初学者。 40 | 41 | CAUTION: 本章节涉及较为进阶内容,建议第一次接触编程或缺乏编程经验的开发者暂时跳过这一章节。 42 | 43 | 除了读之外,一定要 **练习**。软件工程是工程学,是由大量的细节组合起来的工程。如果看完书中的内容,不上机练习,不多加尝试,不勤加思考,那么收益必然是零。因为会有大量的问题在没有尝试之前不会发现,等把书看完了,除了掌握了基本概念,剩下的必然都还在云里雾里。 44 | 45 | === 遇到问题怎么办? 46 | 47 | 如果你从「◯家」买了一套家具回家,发现不会安装怎么办?你必然是先要仔仔细细检查安装说明书,如果实在不行,再打电话给「◯家」,写代码也是这样。大家的时间通常都是有限而宝贵的,如果遇到什么鸡毛蒜皮的事情,都问别人,很可能会得到一句愤怒的 RTFM 作为回答。这是 Reading the F**king Manual 的缩写,意思是「去读**的手册」。而且这也会影响到自己独立解决程序问题能力的养成,绝对不是好习惯。对于遇到程序问题,一般的解决流程是: 48 | 49 | 1. 检查开发手册。对于 Ruby 这一手册通常是 Ruby 的 https://ruby-doc.org/[官方文档],对于 Linux/UNIX 系统调用,手册可以通过 `man 调用名称` 和 `info 调用名称` 的系统命令来查询。 50 | 2. 使用搜索引擎查找问题。除非你在使用非常先进的技术,通常你不会是第一个遇到这个问题的。许多网站,例如 https://stackoverflow.com/[stack overflow] 就大量收录了程序相关的问题。上网找找,有极大的概率有人会遇到和你同样的问题,并且已经有解决方案了。 51 | 3. 问问小黄鸭。很多看起来很奇怪的问题往往来源于粗心。放一只橡皮小黄鸭在桌子上,给它一行一行耐心解释你所写的代码,你可能会突然发现问题所在。 52 | 4. 询问行业专家或开发者。如果真的不幸全部都没有解决问题,确实有必要找个人问问。在网上问问题是个很好的途径,一方面你可以问到平时不容易接触到的领域专家,同时也可以留下记录帮助更多人。但是注意,一定要把自己问题的触发条件、相关的环境配置、代码、遇到问题的详情好好描述出来。如果就说一句「我做了 xxx,它不工作」,别人就算看到也无从下手。具体如何提问可以参考 stack overflow 的 https://stackoverflow.com/help/how-to-ask[《How do I ask a good question》]。 53 | 54 | [TIP] 55 | .豆知识:`man` 命令和 `info` 命令有什么区别? 56 | ==== 57 | `man` 是在相当早年的 UNIX 系统中就已经内建在系统中了。而 `info` 则是 GNU 项目的文档系统。`info` 使用 Texinfo 作为其源文件的形式,提供了更加丰富的文本格式,通常内容也会比 `man` 来得更完整。但在长长的 `info` 页面中通过快捷键导航快速找到自己想要的东西也是一件需要熟悉的事情。 58 | ==== 59 | 60 | [TIP] 61 | .闲聊:如果我觉得 Manual 的缩写 `man` 命令冒犯到了我的性别平等主义怎么办? 62 | ==== 63 | 如果你是用 zsh 作为自己的 shell,zsh 有非常有用的 `alias` 功能可以来解决这一问题。把这些东西加入到你的 zsh 配置文件中就好: 64 | 65 | [source,zsh] 66 | ---- 67 | alias man="info" 68 | alias woman="info" 69 | alias lgbt="info" 70 | alias lgbtq="info" 71 | alias lgbtqi="info" 72 | alias lgbtqia="info" 73 | alias lgbtqiap="info" 74 | alias lgbtqiapk="info" 75 | ---- 76 | ==== 77 | -------------------------------------------------------------------------------- /tests/chapter01.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | require 'open3' 3 | require 'minitest/autorun' 4 | 5 | def capture_stdout(&block) 6 | original_stdout = $stdout 7 | $stdout = fake = StringIO.new 8 | begin 9 | yield 10 | ensure 11 | $stdout = original_stdout 12 | end 13 | fake.string 14 | end 15 | 16 | class TestChapter01 < Minitest::Test 17 | # Chapter 01, Editor 18 | def test_print_hello_world_and_returns_nil 19 | output = capture_stdout { puts 'Hello World' } 20 | assert_equal output, "Hello World\n" 21 | assert_nil (puts 'Hello World') 22 | end 23 | 24 | # Chapter 01, Editor 25 | def test_the_combination_of_print_puts_and_p 26 | output = capture_stdout do 27 | print 'Hello' 28 | puts 'Hello' 29 | p 'Hello' 30 | end 31 | assert_equal output, "HelloHello\n\"Hello\"\n" 32 | end 33 | 34 | 35 | # Chapter 01, Variables 36 | def test_bool_operations 37 | assert_equal (true and true), true 38 | assert_equal (true and false), false 39 | assert_equal (false and true), false 40 | assert_equal (false and false), false 41 | 42 | assert_equal (true or true), true 43 | assert_equal (true or false), true 44 | assert_equal (false or true), true 45 | assert_equal (false or false), false 46 | 47 | assert_equal (not true), false 48 | assert_equal (not false), true 49 | end 50 | 51 | # Chapter 01, Variables 52 | def test_bool_ops_with_sym 53 | assert_equal (true && true), true 54 | assert_equal (true && false), false 55 | assert_equal (false && true), false 56 | assert_equal (false && false), false 57 | 58 | assert_equal (true || true), true 59 | assert_equal (true || false), true 60 | assert_equal (false || true), true 61 | assert_equal (false || false), false 62 | 63 | assert_equal !true, false 64 | assert_equal !false, true 65 | end 66 | 67 | # Chapter 01, Variables 68 | def test_bool_ops_practice 69 | assert_equal (true && (true and not false) or !false || false), true 70 | end 71 | 72 | # Chapter 01, Variables 73 | def test_int_ops 74 | assert_equal 1 + 1, 2 75 | assert_equal 1 - 5, -4 76 | assert_equal 3 * 7, 21 77 | assert_equal 6 / 2, 3 78 | assert_equal 5 / 2, 2 79 | assert_equal 5 % 2, 1 80 | assert_equal (-5 % 2), 1 81 | 82 | assert_equal 5 % -2, -1 83 | assert_equal (-5 % -2), -1 84 | assert_raises ZeroDivisionError do 85 | 3 / 0 86 | end 87 | assert_equal 10000000000000000000 * 10000000000000000000, 100000000000000000000000000000000000000 88 | assert_equal 2**256, 115792089237316195423570985008687907853269984665640564039457584007913129639936 89 | end 90 | 91 | # Chapter 01, Variables 92 | def test_local_variables 93 | a = 1 94 | assert_equal a + 1, 2 95 | assert_equal a + 3, 4 96 | a = 2 97 | assert_equal a + 1, 3 98 | assert_equal a + 3, 5 99 | assert_raises SyntaxError do 100 | eval('2 = a') 101 | end 102 | end 103 | 104 | # Chapter 01, Variables (Answers) 105 | def test_local_variables_answers 106 | assert_equal(capture_stdout do 107 | a = 1 108 | b = 1 109 | puts a + b 110 | end, "2\n") 111 | 112 | assert_equal(capture_stdout do 113 | a = 1 114 | b = 1 115 | puts a * b 116 | end, "1\n") 117 | 118 | a = 1 119 | b = 1 120 | c = a + b 121 | assert_equal c, 2 122 | end 123 | 124 | # Chapter 01, Consts 125 | def test_consts 126 | assert_equal `ruby -e "CONST_A = 'foo';CONST_A = 'bar';puts CONST_A"`, "bar\n" 127 | end 128 | 129 | # Chapter 01, Consts 130 | def test_freeze 131 | assert_raises FrozenError do 132 | a = 'foo'.freeze 133 | a << 'bar' 134 | end 135 | 136 | a = 'foo'.freeze 137 | a = 'bar' 138 | assert_equal(a, 'bar') 139 | end 140 | 141 | # Chapter 01, Functions 142 | def test_func_with_single_arg 143 | assert_equal(proc do 144 | def succ(x) 145 | x + 1 146 | end 147 | 148 | succ(1) 149 | end.call, 2) 150 | end 151 | 152 | # Chapter 01, Functions 153 | def test_func_with_no_args 154 | assert_equal(proc do 155 | def hello 156 | 'Hello' 157 | end 158 | 159 | hello 160 | end.call, 'Hello') 161 | end 162 | 163 | # Chapter 01, Functions 164 | def test_func_with_multi_args 165 | assert_equal(proc do 166 | def add(a, b) 167 | a + b 168 | end 169 | 170 | add(1, 2) 171 | end.call, 3) 172 | end 173 | 174 | # Chapter 01, Functions 175 | def test_func_with_early_stop 176 | assert_equal(proc do 177 | def add(a, b) 178 | return a + b 179 | a - b 180 | end 181 | 182 | add(1, 1) 183 | end.call, 2) 184 | end 185 | 186 | # Chapter 01, Functions 187 | def test_func_call 188 | assert_equal(proc do 189 | def add(a, b) 190 | return a + b 191 | end 192 | 193 | add 1, 1 194 | end.call, 2) 195 | end 196 | 197 | # Chapter 01, Functions 198 | def test_func_nil_return 199 | assert_nil(proc do 200 | def nil_func 201 | return 202 | end 203 | 204 | nil_func 205 | end.call) 206 | end 207 | 208 | # Chapter 01, Functions 209 | def test_undef 210 | assert_raises NameError do 211 | def foo 212 | 'bar' 213 | end 214 | assert_equal(foo, 'bar') 215 | undef foo 216 | foo 217 | end 218 | end 219 | end 220 | --------------------------------------------------------------------------------