├── .github └── workflows │ ├── ci.yml │ ├── deploy.yml │ └── netlify-deploy.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── SUMMARY.md ├── axioms_and_computation.md ├── book.toml ├── conv.md ├── custom.js ├── dependent_type_theory.md ├── deploy.sh ├── highlight.js ├── induction_and_recursion.md ├── inductive_types.md ├── interacting_with_lean.md ├── introduction.md ├── lean-toolchain ├── propositions_and_proofs.md ├── quantifiers_and_equality.md ├── structures_and_records.md ├── tactics.md ├── title_page.md ├── type_classes.md └── unixode.sty /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: mdbook test using latest lean4 bits 2 | 3 | # Controls when the action will run. 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | schedule: 12 | - cron: '0 10 * * *' # 11AM CET/3AM PT 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | Build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | # interferes with lean4-nightly authentication 23 | persist-credentials: false 24 | submodules: true 25 | - name: Set paths 26 | run: | 27 | echo "$HOME/.elan/bin" >> $GITHUB_PATH 28 | - name: Setup elan toolchain on this build 29 | run: | 30 | curl -O --location https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh 31 | chmod u+x elan-init.sh 32 | ./elan-init.sh -y --default-toolchain leanprover/lean4:nightly 33 | echo Checking location "$HOME/.elan/bin"... 34 | ls -al "$HOME/.elan/bin" 35 | elan toolchain list 36 | - name: Download mdbook for Lean 37 | run: | 38 | curl -O --location https://github.com/leanprover/mdBook/releases/download/v0.4.15l2/mdbook-linux.tar.gz 39 | tar xvf mdbook-linux.tar.gz 40 | ./mdbook-linux/mdbook --help 41 | ldd ./mdbook-linux/mdbook 42 | - name: Run mdbook test 43 | run: | 44 | ls -al 45 | ./mdbook-linux/mdbook test 46 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: mdbook deploy to github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | Build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Download mdbook for Lean 15 | shell: bash 16 | run: | 17 | curl -O --location https://github.com/leanprover/mdBook/releases/download/v0.4.15l2/mdbook-linux.tar.gz 18 | tar xvf mdbook-linux.tar.gz 19 | ./mdbook-linux/mdbook --help 20 | ldd ./mdbook-linux/mdbook 21 | - name: Run mdbook build 22 | shell: bash 23 | run: | 24 | ./mdbook-linux/mdbook build 25 | rm -rf ./out/.git 26 | rm -rf ./out/.github 27 | - name: Deploy 🚀 28 | uses: JamesIves/github-pages-deploy-action@4.1.5 29 | with: 30 | branch: gh-pages 31 | folder: out 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/netlify-deploy.yml: -------------------------------------------------------------------------------- 1 | name: mdbook deploy to Netlify 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | Build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Download mdbook for Lean 18 | shell: bash 19 | run: | 20 | curl -O --location https://github.com/leanprover/mdBook/releases/download/v0.4.6/mdbook-linux.tar.gz 21 | tar xvf mdbook-linux.tar.gz 22 | ./mdbook-linux/mdbook --help 23 | ldd ./mdbook-linux/mdbook 24 | - name: Run mdbook build 25 | shell: bash 26 | run: | 27 | ./mdbook-linux/mdbook build 28 | rm -rf ./out/.git 29 | rm -rf ./out/.github 30 | - name: Deploy to Netlify hosting 31 | uses: nwtgck/actions-netlify@v2.0 32 | with: 33 | publish-dir: out 34 | production-branch: master 35 | github-token: ${{ secrets.GITHUB_TOKEN }} 36 | deploy-message: | 37 | ${{ github.event_name == 'pull_request' && format('pr#{0}: {1}', github.event.number, github.event.pull_request.title) || format('ref/{0}: {1}', github.ref_name, steps.deploy-info.outputs.message) }} 38 | alias: ${{ steps.deploy-info.outputs.alias }} 39 | enable-commit-comment: false 40 | enable-pull-request-comment: false 41 | github-deployment-environment: "lean-lang.org/theorem_proving_in_lean4" 42 | fails-without-credentials: true 43 | env: 44 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 45 | NETLIFY_SITE_ID: "51300c89-2f6c-4bba-a575-8cf12e434502" 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.olean 2 | /_target 3 | /leanpkg.path 4 | out/ 5 | .vs/ 6 | .vscode/ 7 | /src_zh -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lean-zh/tp-lean-zh/eaccb921ae4f1c35b771e0ab82d76534e87be159/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Theorem Proving in Lean 4 2 | ----------------------- 3 | 4 | This manual is generated by [mdBook](https://github.com/rust-lang/mdBook). We are currently using a 5 | [fork](https://github.com/leanprover/mdBook) of it for the following additional features: 6 | 7 | * Add support for hiding lines in other languages [#1339](https://github.com/rust-lang/mdBook/pull/1339) 8 | * Replace calling `rustdoc --test` from `mdbook test` with `./test` 9 | 10 | To build this manual, first install the fork via 11 | ```bash 12 | cargo install --git https://github.com/leanprover/mdBook mdbook 13 | ``` 14 | Then use e.g. [`mdbook watch`](https://rust-lang.github.io/mdBook/cli/watch.html) in the root folder: 15 | ```bash 16 | mdbook watch --open # opens the output in `out/` in your default browser 17 | ``` 18 | 19 | Run `mdbook test` to test all `lean` code blocks. 20 | 21 | ## How to deploy 22 | 23 | ``` 24 | ./deploy.sh 25 | ``` 26 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Lean 4 定理证明 21 | 22 | [Lean 4 定理证明](./title_page.md) 23 | 24 | - [简介](./introduction.md) 25 | - [依值类型论](./dependent_type_theory.md) 26 | - [命题与证明](./propositions_and_proofs.md) 27 | - [量词与等价](./quantifiers_and_equality.md) 28 | - [证明策略](./tactics.md) 29 | - [与 Lean 交互](./interacting_with_lean.md) 30 | - [归纳类型](./inductive_types.md) 31 | - [归纳与递归](./induction_and_recursion.md) 32 | - [结构体与记录](./structures_and_records.md) 33 | - [类型类](./type_classes.md) 34 | - [转换策略模式](./conv.md) 35 | - [公理与计算](./axioms_and_computation.md) 36 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Jeremy Avigad", "Leonardo de Moura", "Soonho Kong", "Sebastian Ullrich"] 3 | translators = ["subfish-zhou", "Oling Cat"] 4 | language = "zh-CN" 5 | multilingual = true 6 | src = "." 7 | title = "Lean 4 定理证明" 8 | 9 | [build] 10 | build-dir = "out" 11 | 12 | [output.html] 13 | # additional-css = ["custom.css", "pygments.css"] 14 | additional-js = ["custom.js"] 15 | mathjax-support = true 16 | site-url = "/tp-lean-zh/" 17 | git-repository-url = "https://github.com/Lean-zh/tp-lean-zh" 18 | edit-url-template = "https://github.com/Lean-zh/tp-lean-zh/edit/master/{path}" 19 | 20 | [output.html.playground.boring-prefixes] 21 | lean = "# " 22 | -------------------------------------------------------------------------------- /conv.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 转换策略模式 7 | ========================= 8 | 9 | 15 | 16 | 在策略块中,可以使用关键字`conv`进入转换模式(conversion mode)。这种模式允许在假设和目标内部,甚至在函数抽象和依赖箭头内部移动,以应用重写或简化步骤。 17 | 18 | 22 | 23 | 基本导航和重写 24 | ------- 25 | 26 | 38 | 39 | 作为第一个例子,让我们证明`(a b c : Nat) : a * (b * c) = a * (c * b)`(本段中的例子有些刻意设计,因为其他策略可以立即完成它们)。首次简单的尝试是尝试`rw [Nat.mul_comm]`,但这将目标转化为`b * c * a = a * (c * b)`,因为它作用于项中出现的第一个乘法。有几种方法可以解决这个问题,其中一个方法是 40 | 41 | ```lean 42 | example (a b c : Nat) : a * (b * c) = a * (c * b) := by 43 | rw [Nat.mul_comm b c] 44 | ``` 45 | 46 | 不过本节介绍一个更精确的工具:转换模式。下面的代码块显示了每行之后的当前目标。 47 | 48 | ```lean 49 | example (a b c : Nat) : a * (b * c) = a * (c * b) := by 50 | conv => 51 | -- ⊢ a * (b * c) = a * (c * b) 52 | lhs 53 | -- ⊢ a * (b * c) 54 | congr 55 | -- 2 goals: ⊢ a, ⊢ b * c 56 | rfl 57 | -- ⊢ b * c 58 | rw [Nat.mul_comm] 59 | ``` 60 | 61 | 78 | 79 | 上面这段涉及三个导航指令: 80 | 81 | - `lhs`(left hand side)导航到关系(此处是等式)左边。同理`rhs`导航到右边。 82 | - `congr`创建与当前头函数的(非依赖的和显式的)参数数量一样多的目标(此处的头函数是乘法)。 83 | - `skip`走到下一个目标。 84 | 85 | 一旦到达相关目标,我们就可以像在普通策略模式中一样使用`rw`。 86 | 87 | 使用转换模式的第二个主要原因是在约束器下重写。假设我们想证明`(fun x : Nat => 0 + x) = (fun x => x)`。首次简单的尝试`rw [zero_add]`是失败的。报错: 88 | 89 | ``` 90 | error: tactic 'rewrite' failed, did not find instance of the pattern 91 | in the target expression 92 | 0 + ?n 93 | ⊢ (fun x => 0 + x) = fun x => x 94 | ``` 95 | 96 | (错误:'rewrite'策略失败了,没有找到目标表达式中的模式0 + ?n) 97 | 98 | 101 | 102 | 解决方案为: 103 | 104 | ```lean 105 | example : (fun x : Nat => 0 + x) = (fun x => x) := by 106 | conv => 107 | lhs 108 | intro x 109 | rw [Nat.zero_add] 110 | ``` 111 | 112 | 116 | 117 | 其中`intro x`是导航命令,它进入了`fun`约束器。这个例子有点刻意,你也可以这样做: 118 | 119 | ```lean 120 | example : (fun x : Nat => 0 + x) = (fun x => x) := by 121 | funext x; rw [Nat.zero_add] 122 | ``` 123 | 124 | 127 | 128 | 或者这样: 129 | 130 | ```lean 131 | example : (fun x : Nat => 0 + x) = (fun x => x) := by 132 | simp 133 | ``` 134 | 135 | 138 | 139 | 所有这些也可以用`conv at h`从局部上下文重写一个假设`h`。 140 | 141 | 145 | 146 | 模式匹配 147 | ------- 148 | 149 | 152 | 153 | 使用上面的命令进行导航可能很无聊。使用下面的模式匹配来简化它: 154 | 155 | ```lean 156 | example (a b c : Nat) : a * (b * c) = a * (c * b) := by 157 | conv in b * c => rw [Nat.mul_comm] 158 | ``` 159 | 160 | 163 | 164 | 这是下面代码的语法糖: 165 | 166 | ```lean 167 | example (a b c : Nat) : a * (b * c) = a * (c * b) := by 168 | conv => 169 | pattern b * c 170 | rw [Nat.mul_comm] 171 | ``` 172 | 173 | 176 | 177 | 当然也可以用通配符: 178 | 179 | ```lean 180 | example (a b c : Nat) : a * (b * c) = a * (c * b) := by 181 | conv in _ * c => rw [Nat.mul_comm] 182 | ``` 183 | 184 | 188 | 189 | 结构化转换策略 190 | ------- 191 | 192 | 195 | 196 | 大括号和`.`也可以在`conv`模式下用于结构化策略。 197 | 198 | ```lean 199 | example (a b c : Nat) : (0 + a) * (b * c) = a * (c * b) := by 200 | conv => 201 | lhs 202 | congr 203 | . rw [Nat.zero_add] 204 | . rw [Nat.mul_comm] 205 | ``` 206 | 207 | 211 | 212 | 转换模式中的其他策略 213 | ---------- 214 | 215 | 218 | 219 | - `arg i`进入一个应用的第`i`个非独立显式参数。 220 | 221 | ```lean 222 | example (a b c : Nat) : a * (b * c) = a * (c * b) := by 223 | conv => 224 | -- ⊢ a * (b * c) = a * (c * b) 225 | lhs 226 | -- ⊢ a * (b * c) 227 | arg 2 228 | -- ⊢ b * c 229 | rw [Nat.mul_comm] 230 | ``` 231 | 232 | 237 | 238 | - `args`是`congr`的替代品。 239 | 240 | - `simp`将简化器应用于当前目标。它支持常规策略模式中的相同选项。 241 | 242 | ```lean 243 | def f (x : Nat) := 244 | if x > 0 then x + 1 else x + 2 245 | 246 | example (g : Nat → Nat) (h₁ : g x = x + 1) (h₂ : x > 0) : g x = f x := by 247 | conv => 248 | rhs 249 | simp [f, h₂] 250 | exact h₁ 251 | ``` 252 | 253 | 256 | 257 | - `enter [1, x, 2, y]`是`arg`和`intro`使用给定参数的宏。 258 | 259 | ``` 260 | syntax enterArg := ident <|> group("@"? num) 261 | syntax "enter " "[" (colGt enterArg),+ "]": conv 262 | macro_rules 263 | | `(conv| enter [$i:num]) => `(conv| arg $i) 264 | | `(conv| enter [@$i:num]) => `(conv| arg @$i) 265 | | `(conv| enter [$id:ident]) => `(conv| ext $id) 266 | | `(conv| enter [$arg:enterArg, $args,*]) => `(conv| (enter [$arg]; enter [$args,*])) 267 | ``` 268 | 269 | 280 | 281 | - `done`会失败如果有未解决的目标。 282 | 283 | - `traceState`显示当前策略状态。 284 | 285 | - `whnf` put term in weak head normal form. 286 | 287 | - `tactic => `回到常规策略模式。这对于退出`conv`模式不支持的目标,以及应用自定义的一致性和扩展性引理很有用。 288 | 289 | ```lean 290 | example (g : Nat → Nat → Nat) 291 | (h₁ : ∀ x, x ≠ 0 → g x x = 1) 292 | (h₂ : x ≠ 0) 293 | : g x x + x = 1 + x := by 294 | conv => 295 | lhs 296 | -- ⊢ g x x + x 297 | arg 1 298 | -- ⊢ g x x 299 | rw [h₁] 300 | -- 2 goals: ⊢ 1, ⊢ x ≠ 0 301 | . skip 302 | . tactic => exact h₂ 303 | ``` 304 | 305 | 308 | 309 | - `apply `是`tactic => apply `的语法糖。 310 | 311 | ```lean 312 | example (g : Nat → Nat → Nat) 313 | (h₁ : ∀ x, x ≠ 0 → g x x = 1) 314 | (h₂ : x ≠ 0) 315 | : g x x + x = 1 + x := by 316 | conv => 317 | lhs 318 | arg 1 319 | rw [h₁] 320 | . skip 321 | . apply h₂ 322 | ``` 323 | -------------------------------------------------------------------------------- /custom.js: -------------------------------------------------------------------------------- 1 | const newline = /(?<=[,。、:;」)])\n(?!\n)/gu; 2 | const space = /\s*(<.+?>\p{Script=Han}.+?<\/.+?>)\s*/gu; 3 | const paras = document.querySelectorAll("#content > main > p"); 4 | for (const p of paras) { 5 | p.innerHTML = p.innerHTML.replace(newline, ""); 6 | p.innerHTML = p.innerHTML.replace(space, "$1"); 7 | } 8 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Build 5 | mdbook build 6 | rm -rf out/.git 7 | 8 | # 3. Deploy 9 | rm -rf deploy 10 | mkdir deploy 11 | cd deploy 12 | git init 13 | cp -r ../out/./ . 14 | git add -A 15 | git commit -m "Update `date`" 16 | git push https://github.com/Lean-zh/tp-lean-zh.git +HEAD:gh-pages 17 | cd .. 18 | rm -rf deploy 19 | -------------------------------------------------------------------------------- /interacting_with_lean.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 与 Lean 交互 7 | ===================== 8 | 9 | 22 | 23 | 你现在已经熟悉了依值类型论的基本原理,它既是一种定义数学对象的语言,也是一种构造证明的语言。现在你缺少一个定义新数据类型的机制。下一章介绍*归纳数据类型*的概念来帮你完成这个目标。但首先,在这一章中,我们从类型论的机制中抽身出来,探索与 Lean 交互的一些实用性问题。 24 | 25 | 并非所有的知识都能马上对你有用。可以略过这一节,然后在必要时再回到这一节以了解 Lean 的特性。 26 | 27 | 31 | 32 | 导入文件 33 | --------------- 34 | 35 | 54 | 55 | Lean 的前端的目标是解释用户的输入,构建形式化的表达式,并检查它们是否形式良好和类型正确。Lean 还支持使用各种编辑器,这些编辑器提供持续的检查和反馈。更多信息可以在Lean[文档](https://lean-lang.org/documentation/)上找到。 56 | 57 | Lean 的标准库中的定义和定理分布在多个文件中。用户也可能希望使用额外的库,或在多个文件中开发自己的项目。当 Lean 启动时,它会自动导入库中 `Init` 文件夹的内容,其中包括一些基本定义和结构。因此,我们在这里介绍的大多数例子都是「开箱即用」的。 58 | 59 | 然而,如果你想使用其他文件,需要通过文件开头的`import'语句手动导入。命令 60 | 61 | ``` 62 | import Bar.Baz.Blah 63 | ``` 64 | 65 | 76 | 77 | 导入文件 ``Bar/Baz/Blah.olean``,其中的描述是相对于 Lean **搜索路径** 解释的。关于如何确定搜索路径的信息可以在[文档](https://lean-lang.org/documentation/)中找到。默认情况下,它包括标准库目录,以及(在某些情况下)用户的本地项目的根目录。 78 | 79 | 导入是传递性的。换句话说,如果你导入了 ``Foo``,并且 ``Foo`` 导入了 ``Bar``,那么你也可以访问 ``Bar`` 的内容,而不需要明确导入它。 80 | 81 | 85 | 86 | 小节(续) 87 | ---------------- 88 | 89 | 98 | 99 | Lean 提供了各种分段机制来帮助构造理论。你在[变量和小节](./dependent_type_theory.md#变量和小节)中看到,``section`` 命令不仅可以将理论中的元素组合在一起,还可以在必要时声明变量,作为定理和定义的参数插入。请记住,`variable` 命令的意义在于声明变量,以便在定理中使用,如下面的例子: 100 | 101 | ```lean 102 | section 103 | variable (x y : Nat) 104 | 105 | def double := x + x 106 | 107 | #check double y 108 | #check double (2 * x) 109 | 110 | attribute [local simp] Nat.add_assoc Nat.add_comm Nat.add_left_comm 111 | 112 | theorem t1 : double (x + y) = double x + double y := by 113 | simp [double] 114 | 115 | #check t1 y 116 | #check t1 (2 * x) 117 | 118 | theorem t2 : double (x * y) = double x * y := by 119 | simp [double, Nat.add_mul] 120 | 121 | end 122 | ``` 123 | 124 | 132 | 133 | ``double`` 的定义不需要声明 ``x`` 作为参数;Lean 检测到这种依赖关系并自动插入。同样,Lean 检测到 ``x`` 在 ``t1`` 和 ``t2`` 中的出现,也会自动插入。注意,double **没有** ``y`` 作为参数。变量只包括在实际使用的声明中。 134 | 135 | 139 | 140 | 命名空间(续) 141 | ------------------ 142 | 143 | 152 | 153 | 在 Lean 中,标识符是由层次化的*名称*给出的,如 ``Foo.Bar.baz``。我们在[命名空间](./dependent_type_theory.md#命名空间)一节中看到,Lean 提供了处理分层名称的机制。命令 ``namespace foo`` 导致 ``foo`` 被添加到每个定义和定理的名称中,直到遇到 ``end foo``。命令 ``open foo`` 然后为以 `foo` 开头的定义和定理创建临时的 **别名** 。 154 | 155 | ```lean 156 | namespace Foo 157 | def bar : Nat := 1 158 | end Foo 159 | 160 | open Foo 161 | 162 | #check bar 163 | #check Foo.bar 164 | ``` 165 | 166 | 169 | 170 | 下面的定义 171 | 172 | ```lean 173 | def Foo.bar : Nat := 1 174 | ``` 175 | 176 | 179 | 180 | 被看成一个宏;展开成 181 | 182 | ```lean 183 | namespace Foo 184 | def bar : Nat := 1 185 | end Foo 186 | ``` 187 | 188 | 196 | 197 | 尽管定理和定义的名称必须是唯一的,但标识它们的别名却不是。当我们打开一个命名空间时,一个标识符可能是模糊的。Lean 试图使用类型信息来消除上下文中的含义,但你总是可以通过给出全名来消除歧义。为此,字符串 ``_root_`` 是对空前缀的明确表述。 198 | 199 | ```lean 200 | def String.add (a b : String) : String := 201 | a ++ b 202 | 203 | def Bool.add (a b : Bool) : Bool := 204 | a != b 205 | 206 | def add (α β : Type) : Type := Sum α β 207 | 208 | open Bool 209 | open String 210 | -- #check add -- ambiguous 211 | #check String.add -- String → String → String 212 | #check Bool.add -- Bool → Bool → Bool 213 | #check _root_.add -- Type → Type → Type 214 | 215 | #check add "hello" "world" -- String 216 | #check add true false -- Bool 217 | #check add Nat Nat -- Type 218 | ``` 219 | 220 | 223 | 224 | 我们可以通过使用 ``protected`` 关键字来阻止创建较短的别名: 225 | 226 | ```lean 227 | protected def Foo.bar : Nat := 1 228 | 229 | open Foo 230 | 231 | -- #check bar -- error 232 | #check Foo.bar 233 | ``` 234 | 235 | 241 | 242 | 这通常用于像`Nat.rec`和`Nat.recOn`这样的名称,以防止普通名称的重载。 243 | 244 | ``open`` 命令允许变量。命令 245 | 246 | ```lean 247 | open Nat (succ zero gcd) 248 | #check zero -- Nat 249 | #eval gcd 15 6 -- 3 250 | ``` 251 | 252 | 255 | 256 | 仅对列出的标识符创建了别名。命令 257 | 258 | ```lean 259 | open Nat hiding succ gcd 260 | #check zero -- Nat 261 | -- #eval gcd 15 6 -- error 262 | #eval Nat.gcd 15 6 -- 3 263 | ``` 264 | 265 | 268 | 269 | 给 ``Nat`` 命名空间中 **除了** 被列出的标识符都创建了别名。命令 270 | 271 | ```lean 272 | open Nat renaming mul → times, add → plus 273 | #eval plus (times 2 2) 3 -- 7 274 | ``` 275 | 276 | 281 | 282 | 将`Nat.mul`更名为 `times`,`Nat.add`更名为 `plus`。 283 | 284 | 有时,将别名从一个命名空间导出到另一个命名空间,或者导出到上一层是很有用的。命令 285 | 286 | ```lean 287 | export Nat (succ add sub) 288 | ``` 289 | 290 | 296 | 297 | 在当前命名空间中为 ``succ``、``add`` 和 ``sub`` 创建别名,这样无论何时命名空间被打开,这些别名都可以使用。如果这个命令在名字空间之外使用,那么这些别名会被输出到上一层。 298 | 299 | 303 | 304 | 属性 305 | ---------- 306 | 307 | 327 | 328 | Lean 的主要功能是把用户的输入翻译成形式化的表达式,由内核检查其正确性,然后存储在环境中供以后使用。但是有些命令对环境有其他的影响,或者给环境中的对象分配属性,定义符号,或者声明类型类的实例,如[类型类](./type_classes.md)一章所述。这些命令大多具有全局效应,也就是说,它们不仅在当前文件中保持有效,而且在任何导入它的文件中也保持有效。然而,这类命令通常支持 ``local`` 修饰符,这表明它们只在当前 ``section`` 或 ``namespace`` 关闭前或当前文件结束前有效。 329 | 330 | 在[简化](./tactics.md#简化)一节中,我们看到可以用`[simp]`属性来注释定理,这使它们可以被简化器使用。下面的例子定义了列表的前缀关系,证明了这种关系是自反的,并为该定理分配了`[simp]`属性。 331 | 332 | ```lean 333 | def isPrefix (l₁ : List α) (l₂ : List α) : Prop := 334 | ∃ t, l₁ ++ t = l₂ 335 | 336 | @[simp] theorem List.isPrefix_self (as : List α) : isPrefix as as := 337 | ⟨[], by simp⟩ 338 | 339 | example : isPrefix [1, 2, 3] [1, 2, 3] := by 340 | simp 341 | ``` 342 | 343 | 348 | 349 | 然后简化器通过将其改写为 ``True`` 来证明 ``isPrefix [1, 2, 3] [1, 2, 3]``。 350 | 351 | 你也可以在做出定义后的任何时候分配属性: 352 | 353 | ```lean 354 | # def isPrefix (l₁ : List α) (l₂ : List α) : Prop := 355 | # ∃ t, l₁ ++ t = l₂ 356 | theorem List.isPrefix_self (as : List α) : isPrefix as as := 357 | ⟨[], by simp⟩ 358 | 359 | attribute [simp] List.isPrefix_self 360 | ``` 361 | 362 | 367 | 368 | 在所有这些情况下,该属性在任何导入该声明的文件中仍然有效。添加 ``local`` 修饰符可以限制范围: 369 | 370 | ```lean 371 | # def isPrefix (l₁ : List α) (l₂ : List α) : Prop := 372 | # ∃ t, l₁ ++ t = l₂ 373 | section 374 | 375 | theorem List.isPrefix_self (as : List α) : isPrefix as as := 376 | ⟨[], by simp⟩ 377 | 378 | attribute [local simp] List.isPrefix_self 379 | 380 | example : isPrefix [1, 2, 3] [1, 2, 3] := by 381 | simp 382 | 383 | end 384 | 385 | -- Error: 386 | -- example : isPrefix [1, 2, 3] [1, 2, 3] := by 387 | -- simp 388 | ``` 389 | 390 | 396 | 397 | 另一个例子,我们可以使用 `instance` 命令来给 `isPrefix` 关系分配符号`≤`。该命令将在[类型类](./type_classes.md)中解释,它的工作原理是给相关定义分配一个`[instance]`属性。 398 | 399 | ```lean 400 | def isPrefix (l₁ : List α) (l₂ : List α) : Prop := 401 | ∃ t, l₁ ++ t = l₂ 402 | 403 | instance : LE (List α) where 404 | le := isPrefix 405 | 406 | theorem List.isPrefix_self (as : List α) : as ≤ as := 407 | ⟨[], by simp⟩ 408 | ``` 409 | 410 | 413 | 414 | 这个分配也可以是局部的: 415 | 416 | ```lean 417 | # def isPrefix (l₁ : List α) (l₂ : List α) : Prop := 418 | # ∃ t, l₁ ++ t = l₂ 419 | def instLe : LE (List α) := 420 | { le := isPrefix } 421 | 422 | section 423 | attribute [local instance] instLe 424 | 425 | example (as : List α) : as ≤ as := 426 | ⟨[], by simp⟩ 427 | 428 | end 429 | 430 | -- Error: 431 | -- example (as : List α) : as ≤ as := 432 | -- ⟨[], by simp⟩ 433 | ``` 434 | 435 | 444 | 445 | 在下面的[符号](#符号)一节中,我们将讨论 Lean 定义符号的机制,并看到它们也支持 ``local`` 修饰符。然而,在[设置选项](#设置选项)一节中,我们将讨论 Lean 设置选项的机制,它并 **不** 遵循这种模式:选项 **只能** 在局部设置,也就是说,它们的范围总是限制在当前小节或当前文件中。 446 | 447 | 451 | 452 | 隐参数(续) 453 | -------------------------- 454 | 455 | 479 | 480 | 在[隐参数](./dependent_type_theory.md#隐参数)一节中,我们看到,如果 Lean 将术语 ``t`` 的类型显示为 ``{x : α} → β x``,那么大括号表示 ``x`` 被标记为 ``t`` 的*隐参数*。这意味着每当你写 ``t`` 时,就会插入一个占位符,或者说「洞」,这样 ``t`` 就会被 ``@t _`` 取代。如果你不希望这种情况发生,你必须写 ``@t`` 来代替。 481 | 482 | 请注意,隐参数是急于插入的。假设我们定义一个函数 ``f (x : Nat) {y : Nat} (z : Nat)``。那么,当我们写表达式`f 7`时,没有进一步的参数,它会被解析为`f 7 _`。Lean 提供了一个较弱的注释,``{{y : ℕ}}``,它指定了一个占位符只应在后一个显式参数之前添加。这个注释也可以写成 ``⦃y : Nat⦄``,其中的 unicode 括号输入方式为 ``\{{`` 和 ``\}}``。有了这个注释,表达式`f 7`将被解析为原样,而`f 7 3`将被解析为 ``f 7 _ 3``,就像使用强注释一样。 483 | 484 | 为了说明这种区别,请看下面的例子,它表明一个自反的欧几里得关系既是对称的又是传递的。 485 | 486 | ```lean 487 | def reflexive {α : Type u} (r : α → α → Prop) : Prop := 488 | ∀ (a : α), r a a 489 | 490 | def symmetric {α : Type u} (r : α → α → Prop) : Prop := 491 | ∀ {a b : α}, r a b → r b a 492 | 493 | def transitive {α : Type u} (r : α → α → Prop) : Prop := 494 | ∀ {a b c : α}, r a b → r b c → r a c 495 | 496 | def euclidean {α : Type u} (r : α → α → Prop) : Prop := 497 | ∀ {a b c : α}, r a b → r a c → r b c 498 | 499 | theorem th1 {α : Type u} {r : α → α → Prop} 500 | (reflr : reflexive r) (euclr : euclidean r) 501 | : symmetric r := 502 | fun {a b : α} => 503 | fun (h : r a b) => 504 | show r b a from euclr h (reflr _) 505 | 506 | theorem th2 {α : Type u} {r : α → α → Prop} 507 | (symmr : symmetric r) (euclr : euclidean r) 508 | : transitive r := 509 | fun {a b c : α} => 510 | fun (rab : r a b) (rbc : r b c) => 511 | euclr (symmr rab) rbc 512 | 513 | theorem th3 {α : Type u} {r : α → α → Prop} 514 | (reflr : reflexive r) (euclr : euclidean r) 515 | : transitive r := 516 | th2 (th1 reflr @euclr) @euclr 517 | 518 | variable (r : α → α → Prop) 519 | variable (euclr : euclidean r) 520 | 521 | #check euclr -- r ?m1 ?m2 → r ?m1 ?m3 → r ?m2 ?m3 522 | ``` 523 | 524 | 533 | 534 | 这些结果被分解成几个小步骤。``th1`` 表明自反和欧氏的关系是对称的,``th2`` 表明对称和欧氏的关系是传递的。然后 ``th3`` 结合这两个结果。但是请注意,我们必须手动禁用 `th1`、`th2` 和 `euclr` 中的隐参数,否则会插入太多的隐参数。如果我们使用弱隐式参数,这个问题就会消失: 535 | 536 | ```lean 537 | def reflexive {α : Type u} (r : α → α → Prop) : Prop := 538 | ∀ (a : α), r a a 539 | 540 | def symmetric {α : Type u} (r : α → α → Prop) : Prop := 541 | ∀ {{a b : α}}, r a b → r b a 542 | 543 | def transitive {α : Type u} (r : α → α → Prop) : Prop := 544 | ∀ {{a b c : α}}, r a b → r b c → r a c 545 | 546 | def euclidean {α : Type u} (r : α → α → Prop) : Prop := 547 | ∀ {{a b c : α}}, r a b → r a c → r b c 548 | 549 | theorem th1 {α : Type u} {r : α → α → Prop} 550 | (reflr : reflexive r) (euclr : euclidean r) 551 | : symmetric r := 552 | fun {a b : α} => 553 | fun (h : r a b) => 554 | show r b a from euclr h (reflr _) 555 | 556 | theorem th2 {α : Type u} {r : α → α → Prop} 557 | (symmr : symmetric r) (euclr : euclidean r) 558 | : transitive r := 559 | fun {a b c : α} => 560 | fun (rab : r a b) (rbc : r b c) => 561 | euclr (symmr rab) rbc 562 | 563 | theorem th3 {α : Type u} {r : α → α → Prop} 564 | (reflr : reflexive r) (euclr : euclidean r) 565 | : transitive r := 566 | th2 (th1 reflr euclr) euclr 567 | 568 | variable (r : α → α → Prop) 569 | variable (euclr : euclidean r) 570 | 571 | #check euclr -- euclidean r 572 | ``` 573 | 574 | 579 | 580 | 还有第三种隐式参数,用方括号表示,``[`` 和 ``]``。这些是用于类型类的,在[类型类](./type_classes.md)中解释。 581 | 582 | 586 | 587 | 符号 588 | -------- 589 | 590 | 615 | 616 | Lean 中的标识符可以包括任何字母数字字符,包括希腊字母(除了∀、Σ和λ,它们在依值类型论中有特殊的含义)。它们还可以包括下标,可以通过输入 ``\_``,然后再输入所需的下标字符。 617 | 618 | Lean 的解析器是可扩展的,也就是说,我们可以定义新的符号。 619 | 620 | Lean 的语法可以由用户在各个层面进行扩展和定制,从基本的「mixfix」符号到自定义的繁饰器。事实上,所有内置的语法都是使用对用户开放的相同机制和 API 进行解析和处理的。 在本节中,我们将描述和解释各种扩展点。 621 | 622 | 虽然在编程语言中引入新的符号是一个相对罕见的功能,有时甚至因为它有可能使代码变得模糊不清而被人诟病,但它是形式化的一个宝贵工具,可以在代码中简洁地表达各自领域的既定惯例和符号。 除了基本的符号之外,Lean 的能力是将普通的样板代码分解成(良好的)宏,并嵌入整个定制的特定领域语言(DSL,domain specific language),对子问题进行高效和可读的文本编码,这对程序员和证明工程师都有很大的好处。 623 | 624 | 627 | 628 | ### 符号和优先级 629 | 630 | 634 | 635 | 最基本的语法扩展命令允许引入新的(或重载现有的)前缀、下缀和后缀运算符: 636 | 637 | 647 | 648 | ```lean 649 | infixl:65 " + " => HAdd.hAdd -- 左结合 650 | infix:50 " = " => Eq -- 非结合 651 | infixr:80 " ^ " => HPow.hPow -- 右结合 652 | prefix:100 "-" => Neg.neg 653 | set_option quotPrecheck false 654 | postfix:max "⁻¹" => Inv.inv 655 | ``` 656 | 657 | 668 | 669 | 语法: 670 | 671 | 运算符种类(其「结合方式」) : 解析优先级 " 新的或现有的符号 " => 这个符号应该翻译成的函数 672 | 673 | 优先级是一个自然数,描述一个运算符与它的参数结合的「紧密程度」,编码操作的顺序。我们可以通过查看上述展开的命令来使之更加精确: 674 | 675 | ```lean 676 | notation:65 lhs:65 " + " rhs:66 => HAdd.hAdd lhs rhs 677 | notation:50 lhs:51 " = " rhs:51 => Eq lhs rhs 678 | notation:80 lhs:81 " ^ " rhs:80 => HPow.hPow lhs rhs 679 | notation:100 "-" arg:100 => Neg.neg arg 680 | # set_option quotPrecheck false 681 | notation:1024 arg:1024 "⁻¹" => Inv.inv arg -- `max` is a shorthand for precedence 1024 682 | ``` 683 | 684 | 700 | 701 | 事实证明,第一个代码块中的所有命令实际上都是命令 **宏** ,翻译成更通用的 `notation` 命令。我们将在下面学习如何编写这种宏。 `notation` 命令不接受单一的记号,而是接受一个混合的记号序列和有优先级的命名项占位符,这些占位符可以在`=>`的右侧被引用,并将被在该位置解析的相应项所取代。 优先级为 `p` 的占位符在该处只接受优先级至少为 `p` 的记号。因此,字符串`a + b + c`不能被解析为等同于`a + (b + c)`,因为 `infixl` 符号的右侧操作数的优先级比该符号本身大。 相反,`infixr` 重用了符号右侧操作数的优先级,所以`a ^ b ^ c` *可以*被解析为`a ^ (b ^ c)`。 注意,如果我们直接使用 `notation` 来引入一个 infix 符号,如 702 | 703 | ```lean 704 | # set_option quotPrecheck false 705 | notation:65 lhs:65 " ~ " rhs:65 => wobble lhs rhs 706 | ``` 707 | 708 | 720 | 721 | 在上文没有充分确定结合规则的情况下,Lean 的解析器将默认为右结合。 更确切地说,Lean 的解析器在存在模糊语法的情况下遵循一个局部的*最长解析*规则:当解析`a ~`中`a ~ b ~ c`的右侧时,它将继续尽可能长的解析(在当前的上下文允许的情况下),不在 `b` 之后停止,而是同时解析`~ c`。因此该术语等同于`a ~ (b ~ c)`。 722 | 723 | 如上所述,`notation` 命令允许我们定义任意的*mixfix*语法,自由地混合记号和占位符。 724 | 725 | ```lean 726 | # set_option quotPrecheck false 727 | notation:max "(" e ")" => e 728 | notation:10 Γ " ⊢ " e " : " τ => Typing Γ e τ 729 | ``` 730 | 731 | 735 | 736 | 没有优先级的占位符默认为 `0`,也就是说,它们接受任何优先级的符号来代替它们。如果两个记号重叠,我们再次应用最长解析规则: 737 | 738 | ```lean 739 | notation:65 a " + " b:66 " + " c:66 => a + b - c 740 | #eval 1 + 2 + 3 -- 0 741 | ``` 742 | 743 | 750 | 751 | 新的符号比二进制符号要好,因为后者在连锁之前,会在`1 + 2`之后停止解析。 如果有多个符号接受同一个最长的解析,选择将被推迟到阐述,这将失败,除非正好有一个重载是类型正确的。 752 | 753 | 757 | 758 | 强制转换 759 | --------- 760 | 761 | 768 | 769 | 在 Lean 中,自然数的类型 ``Nat``,与整数的类型 ``Int`` 不同。但是有一个函数 ``Int.ofNat`` 将自然数嵌入整数中,这意味着我们可以在需要时将任何自然数视为整数。Lean 有机制来检测和插入这种 **强制转换** 。 770 | 771 | ```lean 772 | variable (m n : Nat) 773 | variable (i j : Int) 774 | 775 | #check i + m -- i + Int.ofNat m : Int 776 | #check i + m + j -- i + Int.ofNat m + j : Int 777 | #check i + m + n -- i + Int.ofNat m + Int.ofNat n : Int 778 | ``` 779 | 780 | 784 | 785 | 显示信息 786 | ---------------------- 787 | 788 | 800 | 801 | 有很多方法可以让你查询 Lean 的当前状态以及当前上下文中可用的对象和定理的信息。你已经看到了两个最常见的方法,`#check`和`#eval`。请记住,`#check`经常与`@`操作符一起使用,它使定理或定义的所有参数显式化。此外,你可以使用`#print`命令来获得任何标识符的信息。如果标识符表示一个定义或定理,Lean 会打印出符号的类型,以及它的定义。如果它是一个常数或公理,Lean 会指出它并显示其类型。 802 | 803 | 826 | 827 | ```lean 828 | -- 等式 829 | #check Eq 830 | #check @Eq 831 | #check Eq.symm 832 | #check @Eq.symm 833 | 834 | #print Eq.symm 835 | 836 | -- 与 837 | #check And 838 | #check And.intro 839 | #check @And.intro 840 | 841 | -- 自定义函数 842 | def foo {α : Type u} (x : α) : α := x 843 | 844 | #check foo 845 | #check @foo 846 | #print foo 847 | ``` 848 | 849 | 853 | 854 | 设置选项 855 | --------------- 856 | 857 | 861 | 862 | Lean 维护着一些内部变量,用户可以通过设置这些变量来控制其行为。语法如下: 863 | 864 | ``` 865 | set_option 866 | ``` 867 | 868 | 871 | 872 | 有一系列非常有用的选项可以控制 Lean 的 **美观打印** 显示项的方式。下列选项的输入值为真或假: 873 | 874 | ``` 875 | pp.explicit : display implicit arguments 876 | pp.universes : display hidden universe parameters 877 | pp.notation : display output using defined notations 878 | ``` 879 | 880 | 883 | 884 | 例如,以下设置会产生更长的输出: 885 | 886 | ```lean 887 | set_option pp.explicit true 888 | set_option pp.universes true 889 | set_option pp.notation false 890 | 891 | #check 2 + 2 = 4 892 | #reduce (fun x => x + 2) = (fun x => x + 3) 893 | #check (fun x => x + 1) 1 894 | ``` 895 | 896 | 904 | 905 | 命令 ``set_option pp.all true`` 一次性执行这些设置,而 ``set_option pp.all false`` 则恢复到之前的值。当你调试一个证明,或试图理解一个神秘的错误信息时,漂亮地打印额外的信息往往是非常有用的。不过太多的信息可能会让人不知所措,Lean 的默认值一般来说对普通的交互是足够的。 906 | 907 | 940 | 941 | 945 | 946 | 使用库 947 | ----------------- 948 | 949 | 966 | 967 | 为了有效地使用Lean,你将不可避免地需要使用库中的定义和定理。回想一下,文件开头的 ``import`` 命令会从其他文件中导入之前编译的结果,而且导入是传递的;如果你导入了 ``Foo``,``Foo`` 又导入了 ``Bar``,那么 ``Bar`` 的定义和定理也可以被你利用。但是打开一个命名空间的行为,提供了更短的名字,并没有延续下去。在每个文件中,你需要打开你想使用的命名空间。 968 | 969 | 一般来说,你必须熟悉库和它的内容,这样你就知道有哪些定理、定义、符号和资源可供你使用。下面我们将看到 Lean 的编辑器模式也可以帮助你找到你需要的东西,但直接研究库的内容往往是不可避免的。Lean 的标准库在 GitHub 上。 970 | 971 | - [https://github.com/leanprover/lean4/tree/master/src/Init](https://github.com/leanprover/lean4/tree/master/src/Init) 972 | 973 | - [https://github.com/leanprover/std4/tree/main/Std](https://github.com/leanprover/std4/tree/main/Std) 974 | 975 | 990 | 991 | 你可以使用 GitHub 的浏览器界面查看这些目录和文件的内容。如果你在自己的电脑上安装了Lean,你可以在 ``lean`` 文件夹中找到这个库,用你的文件管理器探索它。每个文件顶部的注释标题提供了额外的信息。 992 | 993 | Lean 库的开发者遵循一般的命名准则,以便于猜测你所需要的定理的名称,或者在支持 Lean 模式的编辑器中用 Tab 自动补全来找到它,这将在下一节讨论。标识符一般是 ``camelCase``,而类型是 `CamelCase`。对于定理的名称,我们依靠描述性的名称,其中不同的组成部分用 `_` 分开。通常情况下,定理的名称只是描述结论。 994 | 995 | ```lean 996 | #check Nat.succ_ne_zero 997 | #check Nat.zero_add 998 | #check Nat.mul_one 999 | #check Nat.le_of_succ_le_succ 1000 | ``` 1001 | 1002 | 1014 | 1015 | Lean 中的标识符可以被组织到分层的命名空间中。例如,命名空间 ``Nat`` 中名为 ``le_of_succ_le_succ`` 的定理有全称 ``Nat.le_of_succ_le_succ``,但较短的名称可由命令 ``open Nat`` 提供(对于未标记为 `protected` 的名称)。我们将在[归纳类型](./inductive_types.md)和[结构体和记录](./structures_and_records.md)中看到,在 Lean 中定义结构体和归纳数据类型会产生相关操作,这些操作存储在与被定义类型同名的命名空间。例如,乘积类型带有以下操作: 1016 | 1017 | ```lean 1018 | #check @Prod.mk 1019 | #check @Prod.fst 1020 | #check @Prod.snd 1021 | #check @Prod.rec 1022 | ``` 1023 | 1024 | 1036 | 1037 | 第一个用于构建一个对,而接下来的两个,``Prod.fst`` 和 ``Prod.snd``,投影两个元素。最后一个,``Prod.rec``,提供了另一种机制,用两个元素的函数来定义乘积上的函数。像 ``Prod.rec`` 这样的名字是*受保护*的,这意味着即使 ``Prod`` 名字空间是打开的,也必须使用全名。 1038 | 1039 | 由于命题即类型的对应原则,逻辑连接词也是归纳类型的实例,因此我们也倾向于对它们使用点符号: 1040 | 1041 | ```lean 1042 | #check @And.intro 1043 | #check @And.casesOn 1044 | #check @And.left 1045 | #check @And.right 1046 | #check @Or.inl 1047 | #check @Or.inr 1048 | #check @Or.elim 1049 | #check @Exists.intro 1050 | #check @Exists.elim 1051 | #check @Eq.refl 1052 | #check @Eq.subst 1053 | ``` 1054 | 1055 | 1059 | 1060 | 自动约束隐参数 1061 | ----------------- 1062 | 1063 | 1068 | 1069 | 在上一节中,我们已经展示了隐参数是如何使函数更方便使用的。然而,像 `compose` 这样的函数在定义时仍然相当冗长。宇宙多态的 `compose` 比之前定义的函数还要啰嗦。 1070 | 1071 | ```lean 1072 | universe u v w 1073 | def compose {α : Type u} {β : Type v} {γ : Type w} 1074 | (g : β → γ) (f : α → β) (x : α) : γ := 1075 | g (f x) 1076 | ``` 1077 | 1078 | 1081 | 1082 | 你可以通过在定义 `compose` 时提供宇宙参数来避免使用 `universe` 命令。 1083 | 1084 | ```lean 1085 | def compose.{u, v, w} 1086 | {α : Type u} {β : Type v} {γ : Type w} 1087 | (g : β → γ) (f : α → β) (x : α) : γ := 1088 | g (f x) 1089 | ``` 1090 | 1091 | 1097 | 1098 | Lean 4支持一个名为 **自动约束隐参数** 的新特性。它使诸如 `compose` 这样的函数在编写时更加方便。当 Lean 处理一个声明的头时, **如果** 它是一个小写字母或希腊字母,任何未约束的标识符都会被自动添加为隐式参数。有了这个特性,我们可以把 `compose` 写成 1099 | 1100 | ```lean 1101 | def compose (g : β → γ) (f : α → β) (x : α) : γ := 1102 | g (f x) 1103 | 1104 | #check @compose 1105 | -- {β : Sort u_1} → {γ : Sort u_2} → {α : Sort u_3} → (β → γ) → (α → β) → α → γ 1106 | ``` 1107 | 1108 | 1115 | 1116 | 请注意,Lean 使用 `Sort` 而不是 `Type` 推断出了一个更通用的类型。 1117 | 1118 | 虽然我们很喜欢这个功能,并且在实现 Lean 时广泛使用,但我们意识到有些用户可能会对它感到不舒服。因此,你可以使用`set_option autoBoundImplicitLocal false`命令将其禁用。 1119 | 1120 | 1128 | 1129 | ```lean 1130 | set_option autoImplicit false 1131 | /- 这个定义会报错:`unknown identifier` -/ 1132 | -- def compose (g : β → γ) (f : α → β) (x : α) : γ := 1133 | -- g (f x) 1134 | ``` 1135 | 1136 | 1140 | 1141 | 隐式Lambda 1142 | --------------- 1143 | 1144 | 1151 | 1152 | 在Lean 3 stdlib中,我们发现了许多[例子](https://github.com/leanprover/lean/blob/master/library/init/category/reader.lean#L39)包含丑陋的`@`+`_` 惯用法。当我们的预期类型是一个带有隐参数的函数类型,而我们有一个常量(例子中的`reader_t.pure`)也需要隐参数时,就会经常使用这个惯用法。在Lean 4中,繁饰器自动引入了 lambda 来消除隐参数。我们仍在探索这一功能并分析其影响,但到目前为止的结果是非常积极的。下面是上面链接中使用Lean 4隐式 lambda 的例子。 1153 | 1154 | ```lean 1155 | # variable (ρ : Type) (m : Type → Type) [Monad m] 1156 | instance : Monad (ReaderT ρ m) where 1157 | pure := ReaderT.pure 1158 | bind := ReaderT.bind 1159 | ``` 1160 | 1161 | 1166 | 1167 | 用户可以通过使用`@`或用包含`{}`或`[]`的约束标记编写的 lambda 表达式来禁用隐式 lambda 功能。下面是几个例子 1168 | 1169 | 1196 | 1197 | ```lean 1198 | # namespace ex2 1199 | def id1 : {α : Type} → α → α := 1200 | fun x => x 1201 | 1202 | def listId : List ({α : Type} → α → α) := 1203 | (fun x => x) :: [] 1204 | 1205 | -- 这个例子中,隐式 lambda 引入被禁用了,因为在 `fun` 前使用了`@` 1206 | def id2 : {α : Type} → α → α := 1207 | @fun α (x : α) => id1 x 1208 | 1209 | def id3 : {α : Type} → α → α := 1210 | @fun α x => id1 x 1211 | 1212 | def id4 : {α : Type} → α → α := 1213 | fun x => id1 x 1214 | 1215 | -- 这个例子中,隐式 lambda 引入被禁用了,因为使用了绑定记号`{...}` 1216 | def id5 : {α : Type} → α → α := 1217 | fun {α} x => id1 x 1218 | # end ex2 1219 | ``` 1220 | 1221 | 1225 | 1226 | 简单函数语法糖 1227 | ------------------------- 1228 | 1229 | 1235 | 1236 | 在Lean 3中,我们可以通过使用小括号从 infix 运算符中创建简单的函数。例如,`(+1)`是`fun x, x + 1`的语法糖。在Lean 4中,我们用`·`作为占位符来扩展这个符号。这里有几个例子: 1237 | 1238 | ```lean 1239 | # namespace ex3 1240 | #check (· + 1) 1241 | -- fun a => a + 1 1242 | #check (2 - ·) 1243 | -- fun a => 2 - a 1244 | #eval [1, 2, 3, 4, 5].foldl (·*·) 1 1245 | -- 120 1246 | 1247 | def f (x y z : Nat) := 1248 | x + y + z 1249 | 1250 | #check (f · 1 ·) 1251 | -- fun a b => f a 1 b 1252 | 1253 | #eval [(1, 2), (3, 4), (5, 6)].map (·.1) 1254 | -- [1, 3, 5] 1255 | # end ex3 1256 | ``` 1257 | 1258 | 1262 | 1263 | 如同在Lean 3中,符号是用圆括号激活的,lambda抽象是通过收集嵌套的`·`创建的。这个集合被嵌套的小括号打断。在下面的例子中创建了两个不同的 lambda 表达式。 1264 | 1265 | ```lean 1266 | #check (Prod.mk · (· + 1)) 1267 | -- fun a => (a, fun b => b + 1) 1268 | ``` 1269 | 1270 | 1274 | 1275 | 命名参数 1276 | --------------- 1277 | 1278 | 1287 | 1288 | 被命名参数使你可以通过用参数的名称而不是参数列表中的位置来指定参数。 如果你不记得参数的顺序但知道它们的名字,你可以以任何顺序传入参数。当 Lean 未能推断出一个隐参数时,你也可以提供该参数的值。被命名参数还可以通过识别每个参数所代表的内容来提高你的代码的可读性。 1289 | 1290 | ```lean 1291 | def sum (xs : List Nat) := 1292 | xs.foldl (init := 0) (·+·) 1293 | 1294 | #eval sum [1, 2, 3, 4] 1295 | -- 10 1296 | 1297 | example {a b : Nat} {p : Nat → Nat → Nat → Prop} (h₁ : p a b b) (h₂ : b = a) 1298 | : p a a b := 1299 | Eq.subst (motive := fun x => p a x b) h₂ h₁ 1300 | ``` 1301 | 1302 | 1306 | 1307 | 在下面的例子中,我们说明了被命名参数和默认参数之间的交互。 1308 | 1309 | ```lean 1310 | def f (x : Nat) (y : Nat := 1) (w : Nat := 2) (z : Nat) := 1311 | x + y + w - z 1312 | 1313 | example (x z : Nat) : f (z := z) x = x + 1 + 2 - z := rfl 1314 | 1315 | example (x z : Nat) : f x (z := z) = x + 1 + 2 - z := rfl 1316 | 1317 | example (x y : Nat) : f x y = fun z => x + y + 2 - z := rfl 1318 | 1319 | example : f = (fun x z => x + 1 + 2 - z) := rfl 1320 | 1321 | example (x : Nat) : f x = fun z => x + 1 + 2 - z := rfl 1322 | 1323 | example (y : Nat) : f (y := 5) = fun x z => x + 5 + 2 - z := rfl 1324 | 1325 | def g {α} [Add α] (a : α) (b? : Option α := none) (c : α) : α := 1326 | match b? with 1327 | | none => a + c 1328 | | some b => a + b + c 1329 | 1330 | variable {α} [Add α] 1331 | 1332 | example : g = fun (a c : α) => a + c := rfl 1333 | 1334 | example (x : α) : g (c := x) = fun (a : α) => a + x := rfl 1335 | 1336 | example (x : α) : g (b? := some x) = fun (a c : α) => a + x + c := rfl 1337 | 1338 | example (x : α) : g x = fun (c : α) => x + c := rfl 1339 | 1340 | example (x y : α) : g x y = fun (c : α) => x + y + c := rfl 1341 | ``` 1342 | 1343 | 1347 | 1348 | 你可以使用`..`来提供缺少的显式参数作为 `_`。这个功能与被命名参数相结合,对编写模式很有用。下面是一个例子: 1349 | 1350 | ```lean 1351 | inductive Term where 1352 | | var (name : String) 1353 | | num (val : Nat) 1354 | | app (fn : Term) (arg : Term) 1355 | | lambda (name : String) (type : Term) (body : Term) 1356 | 1357 | def getBinderName : Term → Option String 1358 | | Term.lambda (name := n) .. => some n 1359 | | _ => none 1360 | 1361 | def getBinderType : Term → Option Term 1362 | | Term.lambda (type := t) .. => some t 1363 | | _ => none 1364 | ``` 1365 | 1366 | 1370 | 1371 | 当显式参数可以由 Lean 自动推断时,省略号也很有用,而我们想避免一连串的 `_`。 1372 | 1373 | ```lean 1374 | example (f : Nat → Nat) (a b c : Nat) : f (a + b + c) = f (a + (b + c)) := 1375 | congrArg f (Nat.add_assoc ..) 1376 | ``` 1377 | -------------------------------------------------------------------------------- /introduction.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | 简介 6 | ============ 7 | 8 | 11 | 12 | 计算机和定理证明 13 | ---------------- 14 | 15 | 25 | 26 | **形式验证(Formal Verification)** 是指使用逻辑和计算方法来验证用精确的数学术语表达的命题。 27 | 这包括普通的数学定理,以及硬件或软件、网络协议、机械和混合系统中的形式命题。 28 | 在实践中,验证数学命题和验证系统的正确性之间很类似:形式验证用数学术语描述硬件和软件系统, 29 | 在此基础上验证其命题的正确性,这就像定理证明的过程。相反,一个数学定理的证明可能需要冗长的计算, 30 | 在这种情况下,验证定理的真实性需要验证计算过程是否出错。 31 | 32 | 38 | 39 | 二十世纪的逻辑学发展表明,绝大多数传统证明方法可以化为若干基础系统中的一小套公理和规则。 40 | 有了这种简化,计算机能以两种方式帮助建立命题:1)它可以帮助寻找一个证明, 41 | 2)可以帮助验证一个所谓的证明是正确的。 42 | 43 | 53 | 54 | **自动定理证明(Automated theorem proving)** 着眼于「寻找」证明。 55 | **归结原理(Resolution)** 定理证明器、**表格法(tableau)** 定理证明器、 56 | **快速可满足性求解器(Fast satisfiability solvers)** 57 | 等提供了在命题逻辑和一阶逻辑中验证公式有效性的方法; 58 | 还有些系统为特定的语言和问题提供搜索和决策程序, 59 | 例如整数或实数上的线性或非线性表达式; 60 | 像 **SMT(Satisfiability modulo theories,可满足性模理论)** 61 | 这样的架构将通用的搜索方法与特定领域的程序相结合; 62 | 计算机代数系统和专门的数学软件包提供了进行数学计算或寻找数学对象的手段, 63 | 这些系统中的计算也可以被看作是一种证明,而这些系统也可以帮助建立数学命题。 64 | 65 | 75 | 76 | 自动推理系统努力追求能力和效率,但往往牺牲稳定性。这样的系统可能会有 bug, 77 | 而且很难保证它们所提供的结果是正确的。相比之下,**交互式定理证明器(Interactive theorem proving)** 78 | 侧重于「验证」证明,要求每个命题都有合适的公理基础的证明来支持。 79 | 这就设定了非常高的标准:每一条推理规则和每一步计算都必须通过求助于先前的定义和定理来证明, 80 | 一直到基本公理和规则。事实上,大多数这样的系统提供了精心设计的「证明对象」, 81 | 可以传给其他系统并独立检查。构建这样的证明通常需要用户更多的输入和交互, 82 | 但它可以让你获得更深入、更复杂的证明。 83 | 84 | 90 | 91 | **Lean 定理证明器** 旨在融合交互式和自动定理证明, 92 | 它将自动化工具和方法置于一个支持用户交互和构建完整公理化证明的框架中。 93 | 它的目标是支持数学推理和复杂系统的推理,并验证这两个领域的命题。 94 | 95 | 103 | 104 | Lean 的底层逻辑有一个计算的解释,与此同时 Lean 可以被视为一种编程语言。 105 | 更重要的是,它可以被看作是一个编写具有精确语义的程序的系统, 106 | 以及对程序所表示的计算功能进行推理。Lean 中也有机制使它能够作为它自己的 **元编程语言**, 107 | 这意味着你可以使用 Lean 本身实现自动化和扩展 Lean 的功能。 108 | Lean 的这些方面将在本教程的配套教程 109 | [Lean 4函数式编程](https://www.leanprover.cn/fp-lean-zh/)中进行探讨,本书只介绍计算方面。 110 | 111 | 114 | 115 | 关于 Lean 116 | ---------- 117 | 118 | 124 | 125 | *Lean* 项目由微软 Redmond 研究院的 Leonardo de Moura 在 2013 年发起,这是个长期项目, 126 | 自动化方法的潜力会在未来逐步被挖掘。Lean 是在 [Apache 2.0 许可协议](LICENSE) 下发布的, 127 | 这是一个允许他人自由使用和扩展代码和数学库的许可性开源许可证。 128 | 129 | 130 | 137 | 138 | 通常有两种办法来运行Lean。第一个是[网页版本](https://live.lean-lang.org/): 139 | 由 JavaScript 编写,包含标准定义和定理库,编辑器会下载到你的浏览器上运行。 140 | 这是个方便快捷的办法。 141 | 142 | 第二种是本地版本:本地版本远快于网页版本,并且非常灵活。Visual Studio Code 143 | 和 Emacs 扩展模块给编写和调试证明提供了有力支撑,因此更适合正式使用。 144 | 源代码和安装方法见[https://github.com/leanprover/lean4/](https://github.com/leanprover/lean4/). 145 | 146 | 本教程介绍的是 Lean 的当前版本:Lean 4。 147 | 148 | 151 | 152 | 关于本书 153 | --------------- 154 | 155 | 163 | 164 | 165 | 本书的目的是教你在 Lean 中编写和验证证明,并且不太需要针对 Lean 的基础知识。首先,你将学习 Lean 所基于的逻辑系统,它是 **依值类型论(Dependent type theory)** 的一个版本,足以证明几乎所有传统的数学定理,并且有足够的表达能力自然地表示数学定理。更具体地说,Lean 是基于具有归纳类型(Inductive type)的构造演算(Calculus of Construction)系统的类型论版本。Lean 不仅可以定义数学对象和表达依值类型论的数学断言,而且还可以作为一种语言来编写证明。 166 | 167 | 174 | 175 | 由于完全深入细节的公理证明十分复杂,定理证明的难点在于让计算机尽可能多地填补证明细节。 176 | 你将通过[依值类型论](dependent_type_theory.md)语言来学习各种方法实现自动证明,例如项重写, 177 | 以及 Lean 中的项和表达式的自动简化方法。同样,**繁饰(Elaboration)** 178 | 和 **类型推断(Type inference)** 的方法,可以用来支持灵活的代数推理。 179 | 180 | 181 | 187 | 188 | 最后,你会学到 Lean 的一些特性,包括与系统交流的语言,和 Lean 提供的对复杂理论和数据的管理机制。 189 | 190 | 在本书中你会见到类似下面这样的代码: 191 | 192 | ```lean 193 | theorem and_commutative (p q : Prop) : p ∧ q → q ∧ p := 194 | fun hpq : p ∧ q => 195 | have hp : p := And.left hpq 196 | have hq : q := And.right hpq 197 | show q ∧ p from And.intro hq hp 198 | ``` 199 | 200 | 206 | 207 | 如果你在 [VS Code](https://code.visualstudio.com/) 中阅读本书,你会看到一个按钮, 208 | 上面写着「try it!」按下按钮将示例复制到编辑器中,并带有足够的周围上下文,以使代码正确编译。 209 | 您可以在编辑器中输入内容并修改示例,Lean 将在您输入时检查结果并不断提供反馈。 210 | 我们建议您在阅读后面的章节时,自己运行示例并试验代码。你可以通过使用 211 | 「Lean 4: Open Documentation View」命令在 VS Code 中打开本书。 212 | 213 | 致谢 214 | --------------- 215 | 216 | 224 | 225 | 本教程是一项开放源代码项目,在 Github 上维护。许多人为此做出了贡献,提供了 226 | 更正、建议、示例和文本。我们要感谢 Ulrik Buchholz、Kevin Buzzard、Mario Carneiro、 227 | Nathan Carter、Eduardo Cavazos、Amine Chaieb、Joe Corneli、William DeMeo、 228 | Marcus Klaas de Vries、Ben Dyer、Gabriel Ebner、Anthony Hart、Simon Hudon、Sean Leather、 229 | Assia Mahboubi、Gihan Marasingha、Patrick Massot、Christopher John Mazey、 230 | Sebastian Ullrich、Floris van Doorn、Daniel Velleman、Théo Zimmerman、Paul Chisholm、Chris Lovett 231 | 以及 Siddhartha Gadgil 对本文做出的贡献。有关我们杰出的贡献者的最新名单, 232 | 请参见 [Lean 证明器](https://github.com/leanprover/)和 [Lean 社区](https://github.com/leanprover-community/)。 233 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:nightly -------------------------------------------------------------------------------- /propositions_and_proofs.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 命题和证明 7 | ======================= 8 | 9 | 15 | 16 | 前一章你已经看到了在 Lean 中定义对象和函数的一些方法。在本章中,我们还将开始解释如何用依值类型论的语言来编写数学命题和证明。 17 | 18 | 22 | 23 | 命题即类型 24 | --------------------- 25 | 26 | 38 | 39 | 证明在依值类型论语言中定义的对象的断言(assertion)的一种策略是在定义语言之上分层断言语言和证明语言。但是,没有理由以这种方式重复使用多种语言:依值类型论是灵活和富有表现力的,我们也没有理由不能在同一个总框架中表示断言和证明。 40 | 41 | 例如,我们可引入一种新类型 ``Prop``,来表示命题,然后引入用其他命题构造新命题的构造子。 42 | 43 | ```lean 44 | # def Implies (p q : Prop) : Prop := p → q 45 | #check And -- Prop → Prop → Prop 46 | #check Or -- Prop → Prop → Prop 47 | #check Not -- Prop → Prop 48 | #check Implies -- Prop → Prop → Prop 49 | 50 | variable (p q r : Prop) 51 | #check And p q -- Prop 52 | #check Or (And p q) r -- Prop 53 | #check Implies (And p q) (And q p) -- Prop 54 | ``` 55 | 56 | 61 | 62 | 对每个元素 ``p : Prop``,可以引入另一个类型 ``Proof p``,作为 ``p`` 的证明的类型。「公理」是这个类型中的常值。 63 | 64 | ```lean 65 | # def Implies (p q : Prop) : Prop := p → q 66 | # structure Proof (p : Prop) : Type where 67 | # proof : p 68 | #check Proof -- Proof : Prop → Type 69 | 70 | axiom and_comm (p q : Prop) : Proof (Implies (And p q) (And q p)) 71 | 72 | variable (p q : Prop) 73 | #check and_comm p q -- Proof (Implies (And p q) (And q p)) 74 | ``` 75 | 76 | 85 | 86 | 然而,除了公理之外,我们还需要从旧证明中建立新证明的规则。例如,在许多命题逻辑的证明系统中,我们有肯定前件式(modus ponens)推理规则: 87 | 88 | > 如果能证明 ``Implies p q`` 和 ``p``,则能证明 ``q``。 89 | 90 | 我们可以如下地表示它: 91 | 92 | ```lean 93 | # def Implies (p q : Prop) : Prop := p → q 94 | # structure Proof (p : Prop) : Type where 95 | # proof : p 96 | axiom modus_ponens : (p q : Prop) → Proof (Implies p q) → Proof p → Proof q 97 | ``` 98 | 99 | 106 | 107 | 命题逻辑的自然演绎系统通常也依赖于以下规则: 108 | 109 | > 当假设 ``p`` 成立时,如果我们能证明 ``q``. 则我们能证明 ``Implies p q``. 110 | 111 | 我们可以如下地表示它: 112 | 113 | ```lean 114 | # def Implies (p q : Prop) : Prop := p → q 115 | # structure Proof (p : Prop) : Type where 116 | # proof : p 117 | axiom implies_intro : (p q : Prop) → (Proof p → Proof q) → Proof (Implies p q) 118 | ``` 119 | 120 | 207 | 208 | 这个功能让我们可以合理地搭建断言和证明。确定表达式 ``t`` 是 ``p`` 的证明,只需检查 ``t`` 具有类型 ``Proof p``。 209 | 210 | 可以做一些简化。首先,我们可以通过将 ``Proof p`` 和 ``p`` 本身合并来避免重复地写 ``Proof`` 这个词。换句话说,只要我们有 ``p : Prop``,我们就可以把 ``p`` 解释为一种类型,也就是它的证明类型。然后我们可以把 ``t : p`` 读作 ``t`` 是 ``p`` 的证明。 211 | 212 | 此外,我们可以在 ``Implies p q`` 和 ``p → q`` 之间来回切换。换句话说,命题 ``p`` 和 ``q`` 之间的含义对应于一个函数,它将 ``p`` 的任何元素接受为 ``q`` 的一个元素。因此,引入连接词 ``Implies`` 是完全多余的:我们可以使用依值类型论中常见的函数空间构造子 ``p → q`` 作为我们的蕴含概念。 213 | 214 | 这是在构造演算(Calculus of Constructions)中遵循的方法,因此在 Lean 中也是如此。自然演绎证明系统中的蕴含规则与控制函数抽象(abstraction)和应用(application)的规则完全一致,这是*Curry-Howard同构*的一个实例,有时也被称为*命题即类型*。事实上,类型 ``Prop`` 是上一章描述的类型层次结构的最底部 ``Sort 0`` 的语法糖。此外,``Type u`` 也只是 ``Sort (u+1)`` 的语法糖。``Prop`` 有一些特殊的特性,但像其他类型宇宙一样,它在箭头构造子下是封闭的:如果我们有 ``p q : Prop``,那么 ``p → q : Prop``。 215 | 216 | 至少有两种将命题作为类型来思考的方法。对于那些对逻辑和数学中的构造主义者来说,这是对命题含义的忠实诠释:命题 ``p`` 代表了一种数据类型,即构成证明的数据类型的说明。``p`` 的证明就是正确类型的对象 ``t : p``。 217 | 218 | 非构造主义者可以把它看作是一种简单的编码技巧。对于每个命题 ``p``,我们关联一个类型,如果 ``p`` 为假,则该类型为空,如果 ``p`` 为真,则有且只有一个元素,比如 ``*``。在后一种情况中,让我们说(与之相关的类型)``p`` 被*占据*(inhabited)。恰好,函数应用和抽象的规则可以方便地帮助我们跟踪 ``Prop`` 的哪些元素是被占据的。所以构造一个元素 ``t : p`` 告诉我们 ``p`` 确实是正确的。你可以把 ``p`` 的占据者想象成「``p`` 为真」的事实。对 ``p → q`` 的证明使用「``p`` 是真的」这个事实来得到「``q`` 是真的」这个事实。 219 | 220 | 事实上,如果 ``p : Prop`` 是任何命题,那么 Lean 的内核将任意两个元素 ``t1 t2 : p`` 看作定义相等,就像它把 ``(fun x => t) s`` 和 ``t[s/x]`` 看作定义等价。这就是所谓的「证明无关性」(proof irrelevance)。这意味着,即使我们可以把证明 ``t : p`` 当作依值类型论语言中的普通对象,它们除了 ``p`` 是真的这一事实之外,没有其他信息。 221 | 222 | 我们所建议的思考「命题即类型」范式的两种方式在一个根本性的方面有所不同。从构造的角度看,证明是抽象的数学对象,它被依值类型论中的合适表达式所*表示*。相反,如果我们从上述编码技巧的角度考虑,那么表达式本身并不表示任何有趣的东西。相反,是我们可以写下它们并检查它们是否有良好的类型这一事实确保了有关命题是真的。换句话说,表达式*本身*就是证明。 223 | 224 | 在下面的论述中,我们将在这两种说话方式之间来回切换,有时说一个表达式「构造」或「产生」或「返回」一个命题的证明,有时则简单地说它「是」这样一个证明。这类似于计算机科学家偶尔会模糊语法和语义之间的区别,有时说一个程序「计算」某个函数,有时又说该程序「是」该函数。 225 | 226 | 为了用依值类型论的语言正式表达一个数学断言,我们需要展示一个项 ``p : Prop``。为了*证明*该断言,我们需要展示一个项 ``t : p``。Lean 的任务,作为一个证明助手,是帮助我们构造这样一个项 ``t``,并验证它的格式是否良好,类型是否正确。 227 | 228 | 232 | 233 | ## 以「命题即类型」的方式工作 234 | 235 | 240 | 241 | 在「命题即类型」范式中,只涉及 ``→`` 的定理可以通过 lambda 抽象和应用来证明。在 Lean 中,``theorem`` 命令引入了一个新的定理: 242 | 243 | ```lean 244 | variable {p : Prop} 245 | variable {q : Prop} 246 | 247 | theorem t1 : p → q → p := fun hp : p => fun hq : q => hp 248 | ``` 249 | 250 | 284 | 285 | 这与上一章中常量函数的定义完全相同,唯一的区别是参数是 ``Prop`` 的元素,而不是 ``Type`` 的元素。直观地说,我们对 ``p → q → p`` 的证明假设 ``p`` 和 ``q`` 为真,并使用第一个假设(平凡地)建立结论 ``p`` 为真。 286 | 287 | 请注意,``theorem`` 命令实际上是 ``def`` 命令的一个翻版:在命题和类型对应下,证明定理 ``p → q → p`` 实际上与定义关联类型的元素是一样的。对于内核类型检查器,这两者之间没有区别。 288 | 289 | 然而,定义和定理之间有一些实用的区别。正常情况下,永远没有必要展开一个定理的「定义」;通过证明无关性,该定理的任何两个证明在定义上都是相等的。一旦一个定理的证明完成,通常我们只需要知道该证明的存在;证明是什么并不重要。鉴于这一事实,Lean 将证明标记为*不可还原*(irreducible),作为对解析器(更确切地说,是 **繁饰器** )的提示,在处理文件时一般不需要展开它。事实上,Lean 通常能够并行地处理和检查证明,因为评估一个证明的正确性不需要知道另一个证明的细节。 290 | 291 | 和定义一样,``#print`` 命令可以展示一个定理的证明。 292 | 293 | ```lean 294 | # variable {p : Prop} 295 | # variable {q : Prop} 296 | theorem t1 : p → q → p := fun hp : p => fun hq : q => hp 297 | 298 | #print t1 299 | ``` 300 | 301 | 307 | 308 | 注意,lambda抽象 ``hp : p`` 和 ``hq : q`` 可以被视为 ``t1`` 的证明中的临时假设。Lean 还允许我们通过 ``show`` 语句明确指定最后一个项 ``hp`` 的类型。 309 | 310 | ```lean 311 | # variable {p : Prop} 312 | # variable {q : Prop} 313 | theorem t1 : p → q → p := 314 | fun hp : p => 315 | fun hq : q => 316 | show p from hp 317 | ``` 318 | 319 | 328 | 329 | 添加这些额外的信息可以提高证明的清晰度,并有助于在编写证明时发现错误。``show`` 命令只是注释类型,而且在内部,我们看到的所有关于 ``t1`` 的表示都产生了相同的项。 330 | 331 | 与普通定义一样,我们可以将 lambda 抽象的变量移到冒号的左边: 332 | 333 | ```lean 334 | # variable {p : Prop} 335 | # variable {q : Prop} 336 | theorem t1 (hp : p) (hq : q) : p := hp 337 | 338 | #print t1 -- p → q → p 339 | ``` 340 | 341 | 344 | 345 | 现在我们可以把定理 ``t1`` 作为一个函数应用。 346 | 347 | ```lean 348 | # variable {p : Prop} 349 | # variable {q : Prop} 350 | theorem t1 (hp : p) (hq : q) : p := hp 351 | 352 | axiom hp : p 353 | 354 | theorem t2 : q → p := t1 hp 355 | ``` 356 | 357 | 363 | 364 | 这里,``axiom`` 声明假定存在给定类型的元素,因此可能会破坏逻辑一致性。例如,我们可以使用它来假设空类型 `False` 有一个元素: 365 | 366 | 374 | 375 | ```lean 376 | axiom unsound : False 377 | -- false可导出一切 378 | theorem ex : 1 = 0 := 379 | False.elim unsound 380 | ``` 381 | 382 | 390 | 391 | 声明「公理」``hp : p`` 等同于声明 ``p`` 为真,正如 ``hp`` 所证明的那样。应用定理 ``t1 : p → q → p`` 到事实 ``hp : p``(也就是 ``p`` 为真)得到定理 ``t1 hp : q → p``。 392 | 393 | 回想一下,我们也可以这样写定理 ``t1``: 394 | 395 | ```lean 396 | theorem t1 {p q : Prop} (hp : p) (hq : q) : p := hp 397 | 398 | #print t1 399 | ``` 400 | 401 | 407 | 408 | ``t1`` 的类型现在是 ``∀ {p q : Prop}, p → q → p``。我们可以把它理解为「对于每一对命题 ``p q``,我们都有 ``p → q → p``」。例如,我们可以将所有参数移到冒号的右边: 409 | 410 | ```lean 411 | theorem t1 : ∀ {p q : Prop}, p → q → p := 412 | fun {p q : Prop} (hp : p) (hq : q) => hp 413 | ``` 414 | 415 | 419 | 420 | 如果 ``p`` 和 ``q`` 被声明为变量,Lean 会自动为我们推广它们: 421 | 422 | ```lean 423 | variable {p q : Prop} 424 | 425 | theorem t1 : p → q → p := fun (hp : p) (hq : q) => hp 426 | ``` 427 | 428 | 432 | 433 | 事实上,通过命题即类型的对应关系,我们可以声明假设 ``hp`` 为 ``p``,作为另一个变量: 434 | 435 | ```lean 436 | variable {p q : Prop} 437 | variable (hp : p) 438 | 439 | theorem t1 : q → p := fun (hq : q) => hp 440 | ``` 441 | 442 | 454 | 455 | Lean 检测到证明使用 ``hp``,并自动添加 ``hp : p`` 作为前提。在所有情况下,命令 ``#print t1`` 仍然会产生 ``∀ p q : Prop, p → q → p``。这个类型也可以写成 ``∀ (p q : Prop) (hp : p) (hq :q), p``,因为箭头仅仅表示一个箭头类型,其中目标不依赖于约束变量。 456 | 457 | 当我们以这种方式推广 ``t1`` 时,我们就可以将它应用于不同的命题对,从而得到一般定理的不同实例。 458 | 459 | ```lean 460 | theorem t1 (p q : Prop) (hp : p) (hq : q) : p := hp 461 | 462 | variable (p q r s : Prop) 463 | 464 | #check t1 p q -- p → q → p 465 | #check t1 r s -- r → s → r 466 | #check t1 (r → s) (s → r) -- (r → s) → (s → r) → r → s 467 | 468 | variable (h : r → s) 469 | #check t1 (r → s) (s → r) h -- (s → r) → r → s 470 | ``` 471 | 472 | 480 | 481 | 同样,使用命题即类型对应,类型为 ``r → s`` 的变量 ``h`` 可以看作是 ``r → s`` 存在的假设或前提。 482 | 483 | 作为另一个例子,让我们考虑上一章讨论的组合函数,现在用命题代替类型。 484 | 485 | ```lean 486 | variable (p q r s : Prop) 487 | 488 | theorem t2 (h₁ : q → r) (h₂ : p → q) : p → r := 489 | fun h₃ : p => 490 | show r from h₁ (h₂ h₃) 491 | ``` 492 | 493 | 500 | 501 | 作为一个命题逻辑定理,``t2`` 是什么意思? 502 | 503 | 注意,数字 unicode 下标输入方式为 ``\0``,``\1``,``\2``,...。 504 | 505 | 509 | 510 | ## 命题逻辑 511 | 512 | 527 | 528 | Lean 定义了所有标准的逻辑连接词和符号。命题连接词有以下表示法: 529 | 530 | | Ascii | Unicode | 编辑器缩写 | 定义 | 531 | |------------|-----------|--------------------------|--------| 532 | | True | | | True | 533 | | False | | | False | 534 | | Not | ¬ | ``\not``, ``\neg`` | Not | 535 | | /\\ | ∧ | ``\and`` | And | 536 | | \\/ | ∨ | ``\or`` | Or | 537 | | -> | → | ``\to``, ``\r``, ``\imp``| | 538 | | <-> | ↔ | ``\iff``, ``\lr`` | Iff | 539 | 540 | 它们都接收 ``Prop`` 值。 541 | 542 | ```lean 543 | variable (p q : Prop) 544 | 545 | #check p → q → p ∧ q 546 | #check ¬p → p ↔ False 547 | #check p ∨ q → q ∨ p 548 | ``` 549 | 550 | 570 | 571 | 操作符的优先级如下:``¬ > ∧ > ∨ > → > ↔``。举例:``a ∧ b → c ∨ d ∧ e`` 意为 ``(a ∧ b) → (c ∨ (d ∧ e))``。``→`` 等二元关系是右结合的。所以如果我们有 ``p q r : Prop``,表达式 ``p → q → r`` 读作「如果 ``p``,那么如果 ``q``,那么 ``r``」。这是 ``p ∧ q → r`` 的柯里化形式。 572 | 573 | 在上一章中,我们观察到 lambda 抽象可以被看作是 ``→`` 的「引入规则」,展示了如何「引入」或建立一个蕴含。应用可以看作是一个「消去规则」,展示了如何在证明中「消去」或使用一个蕴含。其他的命题连接词在 Lean 的库 ``Prelude.core`` 文件中定义。(参见[导入文件](./interacting_with_lean.md#_importing_files)以获得关于库层次结构的更多信息),并且每个连接都带有其规范引入和消去规则。 574 | 575 | 578 | 579 | ### 合取 580 | 581 | 587 | 588 | 表达式 ``And.intro h1 h2`` 是 ``p ∧ q`` 的证明,它使用了 ``h1 : p`` 和 ``h2 : q`` 的证明。通常把 ``And.intro`` 称为*合取引入*规则。下面的例子我们使用 ``And.intro`` 来创建 ``p → q → p ∧ q`` 的证明。 589 | 590 | ```lean 591 | variable (p q : Prop) 592 | 593 | example (hp : p) (hq : q) : p ∧ q := And.intro hp hq 594 | 595 | #check fun (hp : p) (hq : q) => And.intro hp hq 596 | ``` 597 | 598 | 608 | 609 | ``example`` 命令声明了一个没有名字也不会永久保存的定理。本质上,它只是检查给定项是否具有指定的类型。它便于说明,我们将经常使用它。 610 | 611 | 表达式 ``And.left h`` 从 ``h : p ∧ q`` 建立了一个 ``p`` 的证明。类似地,``And.right h`` 是 ``q`` 的证明。它们常被称为左或右*合取消去*规则。 612 | 613 | ```lean 614 | variable (p q : Prop) 615 | 616 | example (h : p ∧ q) : p := And.left h 617 | example (h : p ∧ q) : q := And.right h 618 | ``` 619 | 620 | 623 | 624 | 我们现在可以证明 ``p ∧ q → q ∧ p``: 625 | 626 | ```lean 627 | variable (p q : Prop) 628 | 629 | example (h : p ∧ q) : q ∧ p := 630 | And.intro (And.right h) (And.left h) 631 | ``` 632 | 633 | 655 | 656 | 请注意,引入和消去与笛卡尔积的配对和投影操作类似。区别在于,给定 ``hp : p`` 和 ``hq : q``,``And.intro hp hq`` 具有类型 ``p ∧ q : Prop``,而 ``Prod hp hq`` 具有类型 ``p × q : Type``。``∧`` 和 ``×`` 之间的相似性是Curry-Howard同构的另一个例子,但与蕴涵和函数空间构造子不同,在 Lean 中 ``∧`` 和 ``×`` 是分开处理的。然而,通过类比,我们刚刚构造的证明类似于交换一对中的元素的函数。 657 | 658 | 我们将在[结构体和记录](./structures_and_records.md)一章中看到 Lean 中的某些类型是*Structures*,也就是说,该类型是用单个规范的*构造子*定义的,该构造子从一系列合适的参数构建该类型的一个元素。对于每一组 ``p q : Prop``, ``p ∧ q`` 就是一个例子:构造一个元素的规范方法是将 ``And.intro`` 应用于合适的参数 ``hp : p`` 和 ``hq : q``。Lean 允许我们使用*匿名构造子*表示法 ``⟨arg1, arg2, ...⟩`` 在此类情况下,当相关类型是归纳类型并可以从上下文推断时。特别地,我们经常可以写入 ``⟨hp, hq⟩``,而不是 ``And.intro hp hq``: 659 | 660 | ```lean 661 | variable (p q : Prop) 662 | variable (hp : p) (hq : q) 663 | 664 | #check (⟨hp, hq⟩ : p ∧ q) 665 | ``` 666 | 667 | 677 | 678 | 尖括号可以用 ``\<`` 和 ``\>`` 打出来。 679 | 680 | Lean 提供了另一个有用的语法小工具。给定一个归纳类型 ``Foo`` 的表达式 ``e``(可能应用于一些参数),符号 ``e.bar`` 是 ``Foo.bar e`` 的缩写。这为访问函数提供了一种方便的方式,而无需打开名称空间。例如,下面两个表达的意思是相同的: 681 | 682 | ```lean 683 | variable (xs : List Nat) 684 | 685 | #check List.length xs 686 | #check xs.length 687 | ``` 688 | 689 | 694 | 695 | 给定 ``h : p ∧ q``,我们可以写 ``h.left`` 来表示 ``And.left h`` 以及 ``h.right`` 来表示 ``And.right h``。因此我们可以简写上面的证明如下: 696 | 697 | ```lean 698 | variable (p q : Prop) 699 | 700 | example (h : p ∧ q) : q ∧ p := 701 | ⟨h.right, h.left⟩ 702 | ``` 703 | 704 | 715 | 716 | 在简洁和含混不清之间有一条微妙的界限,以这种方式省略信息有时会使证明更难阅读。但对于像上面这样简单的结构,当 ``h`` 的类型和结构的目标很突出时,符号是干净和有效的。 717 | 718 | 像 ``And.`` 这样的迭代结构是很常见的。Lean 还允许你将嵌套的构造函数向右结合,这样这两个证明是等价的: 719 | 720 | ```lean 721 | variable (p q : Prop) 722 | 723 | example (h : p ∧ q) : q ∧ p ∧ q := 724 | ⟨h.right, ⟨h.left, h.right⟩⟩ 725 | 726 | example (h : p ∧ q) : q ∧ p ∧ q := 727 | ⟨h.right, h.left, h.right⟩ 728 | ``` 729 | 730 | 733 | 734 | 这一点也很常用。 735 | 736 | 739 | 740 | ### 析取 741 | 742 | 748 | 749 | 表达式 ``Or.intro_left q hp`` 从证明 ``hp : p`` 建立了 ``p ∨ q`` 的证明。类似地,``Or.intro_right p hq`` 从证明 ``hq : q`` 建立了 ``p ∨ q`` 的证明。这是左右析取(「或」)引入规则。 750 | 751 | ```lean 752 | variable (p q : Prop) 753 | example (hp : p) : p ∨ q := Or.intro_left q hp 754 | example (hq : q) : p ∨ q := Or.intro_right p hq 755 | ``` 756 | 757 | 766 | 767 | 析取消去规则稍微复杂一点。这个想法是,如果我们想要从 ``p ∨ q`` 证明 ``r``,只需要展示 ``p`` 可以证明 ``r``,且 ``q`` 也可以证明 ``r``。换句话说,它是一种逐情况证明。在表达式 ``Or.elim hpq hpr hqr`` 中,``Or.elim`` 接受三个论证,``hpq : p ∨ q``,``hpr : p → r`` 和 ``hqr : q → r``,生成 ``r`` 的证明。在下面的例子中,我们使用 ``Or.elim`` 证明 ``p ∨ q → q ∨ p``。 768 | 769 | ```lean 770 | variable (p q r : Prop) 771 | 772 | example (h : p ∨ q) : q ∨ p := 773 | Or.elim h 774 | (fun hp : p => 775 | show q ∨ p from Or.intro_right q hp) 776 | (fun hq : q => 777 | show q ∨ p from Or.intro_left p hq) 778 | ``` 779 | 780 | 787 | 788 | 在大多数情况下,``Or.intro_right`` 和 ``Or.intro_left`` 的第一个参数可以由 Lean 自动推断出来。因此,Lean 提供了 ``Or.inr`` 和 ``Or.inl`` 作为 ``Or.intro_right _`` 和 ``Or.intro_left _`` 的缩写。因此,上面的证明项可以写得更简洁: 789 | 790 | ```lean 791 | variable (p q r : Prop) 792 | 793 | example (h : p ∨ q) : q ∨ p := 794 | Or.elim h (fun hp => Or.inr hp) (fun hq => Or.inl hq) 795 | ``` 796 | 797 | 807 | 808 | Lean 的完整表达式中有足够的信息来推断 ``hp`` 和 ``hq`` 的类型。但是在较长的版本中使用类型注释可以使证明更具可读性,并有助于捕获和调试错误。 809 | 810 | 因为 ``Or`` 有两个构造子,所以不能使用匿名构造子表示法。但我们仍然可以写 ``h.elim`` 而不是 ``Or.elim h``,不过你需要注意这些缩写是增强还是降低了可读性: 811 | 812 | ```lean 813 | variable (p q r : Prop) 814 | 815 | example (h : p ∨ q) : q ∨ p := 816 | h.elim (fun hp => Or.inr hp) (fun hq => Or.inl hq) 817 | ``` 818 | 819 | 823 | 824 | 827 | 828 | ### 否定和假言 829 | 830 | 838 | 839 | 否定 ``¬p`` 真正的定义是 ``p → False``,所以我们通过从 ``p`` 导出一个矛盾来获得 ``¬p``。类似地,表达式 ``hnp hp`` 从 ``hp : p`` 和 ``hnp : ¬p`` 产生一个 ``False`` 的证明。下一个例子用所有这些规则来证明 ``(p → q) → ¬q → ¬p``。(``¬`` 符号可以由 ``\not`` 或者 ``\neg`` 来写出。) 840 | 841 | ```lean 842 | variable (p q : Prop) 843 | 844 | example (hpq : p → q) (hnq : ¬q) : ¬p := 845 | fun hp : p => 846 | show False from hnq (hpq hp) 847 | ``` 848 | 849 | 855 | 856 | 连接词 ``False`` 只有一个消去规则 ``False.elim``,它表达了一个事实,即矛盾能导出一切。这个规则有时被称为*ex falso* 【*ex falso sequitur quodlibet*(无稽之谈)的缩写】,或*爆炸原理*。 857 | 858 | ```lean 859 | variable (p q : Prop) 860 | 861 | example (hp : p) (hnp : ¬p) : q := False.elim (hnp hp) 862 | ``` 863 | 864 | 870 | 871 | 假命题导出任意的事实 ``q``,是 ``False.elim`` 的一个隐参数,而且是自动推断出来的。这种从相互矛盾的假设中推导出任意事实的模式很常见,用 ``absurd`` 来表示。 872 | 873 | ```lean 874 | variable (p q : Prop) 875 | 876 | example (hp : p) (hnp : ¬p) : q := absurd hp hnp 877 | ``` 878 | 879 | 882 | 883 | 证明 ``¬p → q → (q → p) → r``: 884 | 885 | ```lean 886 | variable (p q r : Prop) 887 | 888 | example (hnp : ¬p) (hq : q) (hqp : q → p) : r := 889 | absurd (hqp hq) hnp 890 | ``` 891 | 892 | 897 | 898 | 顺便说一句,就像 ``False`` 只有一个消去规则,``True`` 只有一个引入规则 ``True.intro : true``。换句话说,``True`` 就是真,并且有一个标准证明 ``True.intro``。 899 | 900 | 903 | 904 | ### 逻辑等价 905 | 906 | 913 | 914 | 表达式 ``Iff.intro h1 h2`` 从 ``h1 : p → q`` 和 ``h2 : q → p`` 生成了 ``p ↔ q`` 的证明。表达式 ``Iff.mp h`` 从 ``h : p ↔ q`` 生成了 ``p → q`` 的证明。表达式 ``Iff.mpr h`` 从 ``h : p ↔ q`` 生成了 ``q → p`` 的证明。下面是 ``p ∧ q ↔ q ∧ p`` 的证明: 915 | 916 | ```lean 917 | variable (p q : Prop) 918 | 919 | theorem and_swap : p ∧ q ↔ q ∧ p := 920 | Iff.intro 921 | (fun h : p ∧ q => 922 | show q ∧ p from And.intro (And.right h) (And.left h)) 923 | (fun h : q ∧ p => 924 | show p ∧ q from And.intro (And.right h) (And.left h)) 925 | 926 | #check and_swap p q -- p ∧ q ↔ q ∧ p 927 | 928 | variable (h : p ∧ q) 929 | example : q ∧ p := Iff.mp (and_swap p q) h 930 | ``` 931 | 932 | 938 | 939 | 我们可以用匿名构造子表示法来,从正反两个方向的证明,来构建 ``p ↔ q`` 的证明。我们也可以使用 ``.`` 符号连接 ``mp`` 和 ``mpr``。因此,前面的例子可以简写如下: 940 | 941 | ```lean 942 | variable (p q : Prop) 943 | 944 | theorem and_swap : p ∧ q ↔ q ∧ p := 945 | ⟨ fun h => ⟨h.right, h.left⟩, fun h => ⟨h.right, h.left⟩ ⟩ 946 | 947 | example (h : p ∧ q) : q ∧ p := (and_swap p q).mp h 948 | ``` 949 | 950 | 954 | 955 | ## 引入辅助子目标 956 | 957 | 963 | 964 | 这里介绍 Lean 提供的另一种帮助构造长证明的方法,即 ``have`` 结构,它在证明中引入了一个辅助的子目标。下面是一个小例子,改编自上一节: 965 | 966 | ```lean 967 | variable (p q : Prop) 968 | 969 | example (h : p ∧ q) : q ∧ p := 970 | have hp : p := h.left 971 | have hq : q := h.right 972 | show q ∧ p from And.intro hq hp 973 | ``` 974 | 975 | 989 | 990 | 在内部,表达式 ``have h : p := s; t`` 产生项 ``(fun (h : p) => t) s``。换句话说,``s`` 是 ``p`` 的证明,``t`` 是假设 ``h : p`` 的期望结论的证明,并且这两个是由 lambda 抽象和应用组合在一起的。这个简单的方法在构建长证明时非常有用,因为我们可以使用中间的 ``have`` 作为通向最终目标的垫脚石。 991 | 992 | Lean 还支持从目标向后推理的结构化方法,它模仿了普通数学文献中「足以说明某某」(suffices to show)的构造。下一个例子简单地排列了前面证明中的最后两行。 993 | 994 | ```lean 995 | variable (p q : Prop) 996 | 997 | example (h : p ∧ q) : q ∧ p := 998 | have hp : p := h.left 999 | suffices hq : q from And.intro hq hp 1000 | show q from And.right h 1001 | ``` 1002 | 1003 | 1009 | 1010 | ``suffices hq : q`` 给出了两条目标。第一,我们需要证明,通过利用附加假设 ``hq : q`` 证明原目标 ``q ∧ p``,这样足以证明 ``q``,第二,我们需要证明 ``q``。 1011 | 1012 | 1016 | 1017 | ## 经典逻辑 1018 | 1019 | 1027 | 1028 | 到目前为止,我们看到的引入和消去规则都是构造主义的,也就是说,它们反映了基于命题即类型对应的逻辑连接词的计算理解。普通经典逻辑在此基础上加上了排中律 ``p ∨ ¬p``(excluded middle, em)。要使用这个原则,你必须打开经典逻辑命名空间。 1029 | 1030 | ```lean 1031 | open Classical 1032 | 1033 | variable (p : Prop) 1034 | #check em p 1035 | ``` 1036 | 1037 | 1046 | 1047 | 从直觉上看,构造主义的「或」非常强:断言 ``p ∨ q`` 等于知道哪个是真实情况。如果 ``RH`` 代表黎曼猜想,经典数学家愿意断言 ``RH ∨ ¬RH``,即使我们还不能断言析取式的任何一端。 1048 | 1049 | 排中律的一个结果是双重否定消去规则(double-negation elimination, dne): 1050 | 1051 | ```lean 1052 | open Classical 1053 | 1054 | theorem dne {p : Prop} (h : ¬¬p) : p := 1055 | Or.elim (em p) 1056 | (fun hp : p => hp) 1057 | (fun hnp : ¬p => absurd hnp h) 1058 | ``` 1059 | 1060 | 1073 | 1074 | 双重否定消去规则给出了一种证明任何命题 ``p`` 的方法:通过假设 ``¬p`` 来推导出 ``false``,相当于证明了 ``p``。换句话说,双重否定消除允许反证法,这在构造主义逻辑中通常是不可能的。作为练习,你可以试着证明相反的情况,也就是说,证明 ``em`` 可以由 ``dne`` 证明。 1075 | 1076 | 经典公理还可以通过使用 ``em`` 让你获得额外的证明模式。例如,我们可以逐情况进行证明: 1077 | 1078 | ```lean 1079 | open Classical 1080 | variable (p : Prop) 1081 | 1082 | example (h : ¬¬p) : p := 1083 | byCases 1084 | (fun h1 : p => h1) 1085 | (fun h1 : ¬p => absurd h1 h) 1086 | ``` 1087 | 1088 | 1091 | 1092 | 或者你可以用反证法来证明: 1093 | 1094 | ```lean 1095 | open Classical 1096 | variable (p : Prop) 1097 | 1098 | example (h : ¬¬p) : p := 1099 | byContradiction 1100 | (fun h1 : ¬p => 1101 | show False from h h1) 1102 | ``` 1103 | 1104 | 1111 | 1112 | 如果你不习惯构造主义,你可能需要一些时间来了解经典推理在哪里使用。在下面的例子中,它是必要的,因为从一个构造主义的观点来看,知道 ``p`` 和 ``q`` 不同时真并不一定告诉你哪一个是假的: 1113 | 1114 | ```lean 1115 | # open Classical 1116 | # variable (p q : Prop) 1117 | example (h : ¬(p ∧ q)) : ¬p ∨ ¬q := 1118 | Or.elim (em p) 1119 | (fun hp : p => 1120 | Or.inr 1121 | (show ¬q from 1122 | fun hq : q => 1123 | h ⟨hp, hq⟩)) 1124 | (fun hp : ¬p => 1125 | Or.inl hp) 1126 | ``` 1127 | 1137 | 1138 | 1139 | 稍后我们将看到,构造逻辑中 **有** 某些情况允许「排中律」和「双重否定消除律」等,而 Lean 支持在这种情况下使用经典推理,而不依赖于排中律。 1140 | 1141 | Lean 中使用的公理的完整列表见[公理与计算](./axioms_and_computation.md)。 1142 | 1143 | 1147 | 1148 | 逻辑命题的例子 1149 | 1150 | 1155 | 1156 | Lean 的标准库包含了许多命题逻辑的有效语句的证明,你可以自由地在自己的证明中使用这些证明。下面的列表包括一些常见的逻辑等价式。 1157 | 1158 | 1161 | 1162 | 交换律: 1163 | 1164 | 1. ``p ∧ q ↔ q ∧ p`` 1165 | 2. ``p ∨ q ↔ q ∨ p`` 1166 | 1167 | 1170 | 1171 | 结合律: 1172 | 1173 | 3. ``(p ∧ q) ∧ r ↔ p ∧ (q ∧ r)`` 1174 | 4. ``(p ∨ q) ∨ r ↔ p ∨ (q ∨ r)`` 1175 | 1176 | 1179 | 1180 | 分配律: 1181 | 1182 | 5. ``p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r)`` 1183 | 6. ``p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r)`` 1184 | 1185 | 1188 | 1189 | 其他性质: 1190 | 1191 | 7. ``(p → (q → r)) ↔ (p ∧ q → r)`` 1192 | 8. ``((p ∨ q) → r) ↔ (p → r) ∧ (q → r)`` 1193 | 9. ``¬(p ∨ q) ↔ ¬p ∧ ¬q`` 1194 | 10. ``¬p ∨ ¬q → ¬(p ∧ q)`` 1195 | 11. ``¬(p ∧ ¬p)`` 1196 | 12. ``p ∧ ¬q → ¬(p → q)`` 1197 | 13. ``¬p → (p → q)`` 1198 | 14. ``(¬p ∨ q) → (p → q)`` 1199 | 15. ``p ∨ False ↔ p`` 1200 | 16. ``p ∧ False ↔ False`` 1201 | 17. ``¬(p ↔ ¬p)`` 1202 | 18. ``(p → q) → (¬q → ¬p)`` 1203 | 1204 | 1207 | 1208 | 经典推理: 1209 | 1210 | 19. ``(p → r ∨ s) → ((p → r) ∨ (p → s))`` 1211 | 20. ``¬(p ∧ q) → ¬p ∨ ¬q`` 1212 | 21. ``¬(p → q) → p ∧ ¬q`` 1213 | 22. ``(p → q) → (¬p ∨ q)`` 1214 | 23. ``(¬q → ¬p) → (p → q)`` 1215 | 24. ``p ∨ ¬p`` 1216 | 25. ``(((p → q) → p) → p)`` 1217 | 1218 | 1243 | 1244 | ``sorry`` 标识符神奇地生成任何东西的证明,或者提供任何数据类型的对象。当然,作为一种证明方法,它是不可靠的——例如,你可以使用它来证明 ``False``——并且当文件使用或导入依赖于它的定理时,Lean 会产生严重的警告。但它对于增量地构建长证明非常有用。从上到下写证明,用 ``sorry`` 来填子证明。确保 Lean 接受所有的 ``sorry``;如果不是,则有一些错误需要纠正。然后返回,用实际的证据替换每个 ``sorry``,直到做完。 1245 | 1246 | 有另一个有用的技巧。你可以使用下划线 ``_`` 作为占位符,而不是 ``sorry``。回想一下,这告诉 Lean 该参数是隐式的,应该自动填充。如果 Lean 尝试这样做并失败了,它将返回一条错误消息「不知道如何合成占位符」(Don't know how to synthesize placeholder),然后是它所期望的项的类型,以及上下文中可用的所有对象和假设。换句话说,对于每个未解决的占位符,Lean 报告在那一点上需要填充的子目标。然后,你可以通过递增填充这些占位符来构造一个证明。 1247 | 1248 | 这里有两个简单的证明例子作为参考。 1249 | 1250 | 1285 | 1286 | ```lean 1287 | open Classical 1288 | 1289 | -- 分配律 1290 | example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := 1291 | Iff.intro 1292 | (fun h : p ∧ (q ∨ r) => 1293 | have hp : p := h.left 1294 | Or.elim (h.right) 1295 | (fun hq : q => 1296 | show (p ∧ q) ∨ (p ∧ r) from Or.inl ⟨hp, hq⟩) 1297 | (fun hr : r => 1298 | show (p ∧ q) ∨ (p ∧ r) from Or.inr ⟨hp, hr⟩)) 1299 | (fun h : (p ∧ q) ∨ (p ∧ r) => 1300 | Or.elim h 1301 | (fun hpq : p ∧ q => 1302 | have hp : p := hpq.left 1303 | have hq : q := hpq.right 1304 | show p ∧ (q ∨ r) from ⟨hp, Or.inl hq⟩) 1305 | (fun hpr : p ∧ r => 1306 | have hp : p := hpr.left 1307 | have hr : r := hpr.right 1308 | show p ∧ (q ∨ r) from ⟨hp, Or.inr hr⟩)) 1309 | 1310 | -- 需要一点经典推理的例子 1311 | example (p q : Prop) : ¬(p ∧ ¬q) → (p → q) := 1312 | fun h : ¬(p ∧ ¬q) => 1313 | fun hp : p => 1314 | show q from 1315 | Or.elim (em q) 1316 | (fun hq : q => hq) 1317 | (fun hnq : ¬q => absurd (And.intro hp hnq) h) 1318 | ``` 1319 | 1320 | 1324 | 1325 | ## 练习 1326 | 1327 | 1330 | 1331 | 证明以下等式,用真实证明取代「sorry」占位符。 1332 | 1333 | 1363 | 1364 | ```lean 1365 | variable (p q r : Prop) 1366 | 1367 | -- ∧ 和 ∨ 的交换律 1368 | example : p ∧ q ↔ q ∧ p := sorry 1369 | example : p ∨ q ↔ q ∨ p := sorry 1370 | 1371 | -- ∧ 和 ∨ 的结合律 1372 | example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := sorry 1373 | example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := sorry 1374 | 1375 | -- 分配律 1376 | example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := sorry 1377 | example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := sorry 1378 | 1379 | -- 其他性质 1380 | example : (p → (q → r)) ↔ (p ∧ q → r) := sorry 1381 | example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := sorry 1382 | example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := sorry 1383 | example : ¬p ∨ ¬q → ¬(p ∧ q) := sorry 1384 | example : ¬(p ∧ ¬p) := sorry 1385 | example : p ∧ ¬q → ¬(p → q) := sorry 1386 | example : ¬p → (p → q) := sorry 1387 | example : (¬p ∨ q) → (p → q) := sorry 1388 | example : p ∨ False ↔ p := sorry 1389 | example : p ∧ False ↔ False := sorry 1390 | example : (p → q) → (¬q → ¬p) := sorry 1391 | ``` 1392 | 1393 | 1397 | 1398 | 下面这些需要一点经典逻辑。 1399 | 1400 | ```lean 1401 | open Classical 1402 | 1403 | variable (p q r : Prop) 1404 | 1405 | example : (p → q ∨ r) → ((p → q) ∨ (p → r)) := sorry 1406 | example : ¬(p ∧ q) → ¬p ∨ ¬q := sorry 1407 | example : ¬(p → q) → p ∧ ¬q := sorry 1408 | example : (p → q) → (¬p ∨ q) := sorry 1409 | example : (¬q → ¬p) → (p → q) := sorry 1410 | example : p ∨ ¬p := sorry 1411 | example : (((p → q) → p) → p) := sorry 1412 | ``` 1413 | 1414 | 1417 | 1418 | 最后,证明 ``¬(p ↔ ¬p)`` 且不使用经典逻辑。 1419 | -------------------------------------------------------------------------------- /quantifiers_and_equality.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 量词与等价 7 | ======================== 8 | 9 | 15 | 16 | 上一章介绍了构造包含命题连接词的证明方法。在本章中,我们扩展逻辑结构,包括全称量词和存在量词,以及等价关系。 17 | 18 | 22 | 23 | 全称量词 24 | ------------------------ 25 | 26 | 74 | 75 | 如果 ``α`` 是任何类型,我们可以将 ``α`` 上的一元谓词 ``p`` 作为 ``α → Prop`` 类型的对象。在这种情况下,给定 ``x : α``, ``p x`` 表示断言 ``p`` 在 ``x`` 上成立。类似地,一个对象 ``r : α → α → Prop`` 表示 ``α`` 上的二元关系:给定 ``x y : α``,``r x y`` 表示断言 ``x`` 与 ``y`` 相关。 76 | 77 | 全称量词 ``∀ x : α, p x`` 表示,对于每一个 ``x : α``,``p x`` 成立。与命题连接词一样,在自然演绎系统中,「forall」有引入和消去规则。非正式地,引入规则是: 78 | 79 | > 在 ``x : α`` 是任意的情况下,给定 ``p x`` 的证明;就可以得到 ``∀ x : α, p x`` 的证明。 80 | 81 | 消去规则是: 82 | 83 | > 给定 ``∀ x : α, p x`` 的证明和任何项 ``t : α``,就可以得到 ``p t`` 的证明。 84 | 85 | 与蕴含的情况一样,命题即类型。回想依值箭头类型的引入规则: 86 | 87 | > 在 ``x : α`` 是任意的情况下给定类型为 ``β x`` 的项 ``t``,就可以得到 ``(fun x : α => t) : (x : α) → β x``。 88 | 89 | 消去规则: 90 | 91 | > 给定项 ``s : (x : α) → β x`` 和任何项 ``t : α``,就可以得到 ``s t : β t``。 92 | 93 | 在 ``p x`` 具有 ``Prop`` 类型的情况下,如果我们用 ``∀ x : α, p x`` 替换 ``(x : α) → β x``,就得到构建涉及全称量词的证明的规则。 94 | 95 | 因此,构造演算用全称表达式来识别依值箭头类型。如果 ``p`` 是任何表达式,``∀ x : α, p`` 不过是 ``(x : α) → p`` 的替代符号,在 ``p`` 是命题的情况下,前者比后者更自然。通常,表达式 ``p`` 取决于 ``x : α``。回想一下,在普通函数空间中,我们可以将 ``α → β`` 解释为 ``(x : α) → β`` 的特殊情况,其中 ``β`` 不依赖于 ``x``。类似地,我们可以把命题之间的蕴涵 ``p → q`` 看作是 ``∀ x : p, q`` 的特殊情况,其中 ``q`` 不依赖于 ``x``。 96 | 97 | 下面是一个例子,说明了如何运用命题即类型对应规则。``∀`` 可以用 ``\forall`` 输入,也可以用前两个字母简写 ``\fo``。 98 | 99 | ```lean 100 | example (α : Type) (p q : α → Prop) : (∀ x : α, p x ∧ q x) → ∀ y : α, p y := 101 | fun h : ∀ x : α, p x ∧ q x => 102 | fun y : α => 103 | show p y from (h y).left 104 | ``` 105 | 106 | 122 | 123 | 作为一种符号约定,我们给予全称量词尽可能最宽的优先级范围,因此上面例子中的假设中,需要用括号将 ``x`` 上的量词限制起来。证明 ``∀ y : α, p y`` 的标准方法是取任意的 ``y``,然后证明 ``p y``。这是引入规则。现在,给定 ``h`` 有类型 ``∀ x : α, p x ∧ q x``,表达式 ``h y`` 有类型 ``p y ∧ q y``。这是消去规则。取合取的左侧得到想要的结论 ``p y``。 124 | 125 | 只有约束变量名称不同的表达式被认为是等价的。因此,例如,我们可以在假设和结论中使用相同的变量 ``x``,并在证明中用不同的变量 ``z`` 实例化它: 126 | 127 | ```lean 128 | example (α : Type) (p q : α → Prop) : (∀ x : α, p x ∧ q x) → ∀ x : α, p x := 129 | fun h : ∀ x : α, p x ∧ q x => 130 | fun z : α => 131 | show p z from And.left (h z) 132 | ``` 133 | 134 | 137 | 138 | 再举一个例子,下面是关系 ``r`` 的传递性: 139 | 140 | ```lean 141 | variable (α : Type) (r : α → α → Prop) 142 | variable (trans_r : ∀ x y z, r x y → r y z → r x z) 143 | 144 | variable (a b c : α) 145 | variable (hab : r a b) (hbc : r b c) 146 | 147 | #check trans_r -- ∀ (x y z : α), r x y → r y z → r x z 148 | #check trans_r a b c -- r a b → r b c → r a c 149 | #check trans_r a b c hab -- r b c → r a c 150 | #check trans_r a b c hab hbc -- r a c 151 | ``` 152 | 164 | 165 | 当我们在值 ``a b c`` 上实例化 ``trans_r`` 时,我们最终得到 ``r a b → r b c → r a c`` 的证明。将此应用于「假设」``hab : r a b``,我们得到了 ``r b c → r a c`` 的一个证明。最后将它应用到假设 ``hbc`` 中,得到结论 ``r a c`` 的证明。 166 | 167 | ```lean 168 | variable (α : Type) (r : α → α → Prop) 169 | variable (trans_r : ∀ {x y z}, r x y → r y z → r x z) 170 | 171 | variable (a b c : α) 172 | variable (hab : r a b) (hbc : r b c) 173 | 174 | #check trans_r 175 | #check trans_r hab 176 | #check trans_r hab hbc 177 | ``` 178 | 179 | 189 | 190 | 优点是我们可以简单地写 ``trans_r hab hbc`` 作为 ``r a c`` 的证明。一个缺点是 Lean 没有足够的信息来推断表达式 ``trans_r`` 和 ``trans_r hab`` 中的参数类型。第一个 ``#check`` 命令的输出是 ``r ?m.1 ?m.2 → r ?m.2 ?m.3 → r ?m.1 ?m.3``,表示在本例中隐式参数未指定。 191 | 192 | 下面是一个用等价关系进行基本推理的例子: 193 | 194 | ```lean 195 | variable (α : Type) (r : α → α → Prop) 196 | 197 | variable (refl_r : ∀ x, r x x) 198 | variable (symm_r : ∀ {x y}, r x y → r y x) 199 | variable (trans_r : ∀ {x y z}, r x y → r y z → r x z) 200 | 201 | example (a b c d : α) (hab : r a b) (hcb : r c b) (hcd : r c d) : r a d := 202 | trans_r (trans_r hab (symm_r hcb)) hcd 203 | ``` 204 | 205 | 240 | 241 | 为了习惯使用全称量词,你应该尝试本节末尾的一些练习。 242 | 243 | 依值箭头类型的类型规则,特别是全称量词,体现了 ``Prop`` 命题类型与其他对象的类型的不同。假设我们有 ``α : Sort i`` 和 ``β : Sort j``,其中表达式 ``β`` 可能依赖于变量 ``x : α``。那么 ``(x : α) → β`` 是 ``Sort (imax i j)`` 的一个元素,其中 ``imax i j`` 是 ``i`` 和 ``j`` 在 ``j`` 不为0时的最大值,否则为0。 244 | 245 | 其想法如下。如果 ``j`` 不是 ``0``,然后 ``(x : α) → β`` 是 ``Sort (max i j)`` 类型的一个元素。换句话说,从 ``α`` 到 ``β`` 的一类依值函数存在于指数为 ``i`` 和 ``j`` 最大值的宇宙中。然而,假设 ``β`` 属于 ``Sort 0``,即 ``Prop`` 的一个元素。在这种情况下,``(x : α) → β`` 也是 ``Sort 0`` 的一个元素,无论 ``α`` 生活在哪种类型的宇宙中。换句话说,如果 ``β`` 是一个依赖于 ``α`` 的命题,那么 ``∀ x : α, β`` 又是一个命题。这反映出 ``Prop`` 作为一种命题类型而不是数据类型,这也使得 ``Prop`` 具有「非直谓性」(impredicative)。 246 | 247 | 「直谓性」一词起源于20世纪初的数学基础发展,当时逻辑学家如庞加莱和罗素将集合论的悖论归咎于「恶性循环」:当我们通过量化一个集合来定义一个属性时,这个集合包含了被定义的属性。注意,如果 ``α`` 是任何类型,我们可以在 ``α`` 上形成所有谓词的类型 ``α → Prop``(``α`` 的「幂」类型)。Prop的非直谓性意味着我们可以通过 ``α → Prop`` 形成量化命题。特别是,我们可以通过量化所有关于 ``α`` 的谓词来定义 ``α`` 上的谓词,这正是曾经被认为有问题的循环类型。 248 | 249 | 253 | 254 | 等价 255 | -------- 256 | 257 | 265 | 266 | 现在让我们来看看在 Lean 库中定义的最基本的关系之一,即等价关系。在[归纳类型](inductive_types.md)一章中,我们将解释如何从 Lean 的逻辑框架中定义等价。在这里我们解释如何使用它。 267 | 268 | 等价关系的基本性质:反身性、对称性、传递性。 269 | 270 | ```lean 271 | #check Eq.refl -- Eq.refl.{u_1} {α : Sort u_1} (a : α) : a = a 272 | #check Eq.symm -- Eq.symm.{u} {α : Sort u} {a b : α} (h : a = b) : b = a 273 | #check Eq.trans -- Eq.trans.{u} {α : Sort u} {a b c : α} (h₁ : a = b) (h₂ : b = c) : a = c 274 | ``` 275 | 276 | 280 | 281 | 通过告诉 Lean 不要插入隐参数(在这里显示为元变量)可以使输出更容易阅读。 282 | 283 | ```lean 284 | universe u 285 | 286 | #check @Eq.refl.{u} -- @Eq.refl : ∀ {α : Sort u} (a : α), a = a 287 | #check @Eq.symm.{u} -- @Eq.symm : ∀ {α : Sort u} {a b : α}, a = b → b = a 288 | #check @Eq.trans.{u} -- @Eq.trans : ∀ {α : Sort u} {a b c : α}, a = b → b = c → a = c 289 | ``` 290 | 291 | 296 | 297 | ``.{u}`` 告诉 Lean 实例化宇宙 ``u`` 上的常量。 298 | 299 | 因此,我们可以将上一节中的示例具体化为等价关系: 300 | 301 | ```lean 302 | variable (α : Type) (a b c d : α) 303 | variable (hab : a = b) (hcb : c = b) (hcd : c = d) 304 | 305 | example : a = d := 306 | Eq.trans (Eq.trans hab (Eq.symm hcb)) hcd 307 | ``` 308 | 309 | 312 | 313 | 我们也可以使用投影记号: 314 | 315 | ```lean 316 | # variable (α : Type) (a b c d : α) 317 | # variable (hab : a = b) (hcb : c = b) (hcd : c = d) 318 | example : a = d := (hab.trans hcb.symm).trans hcd 319 | ``` 320 | 321 | 328 | 329 | 反身性比它看上去更强大。回想一下,在构造演算中,项有一个计算解释,可规约为相同形式的项会被逻辑框架视为相同的。因此,一些非平凡的恒等式可以通过自反性来证明: 330 | 331 | ```lean 332 | variable (α β : Type) 333 | 334 | example (f : α → β) (a : α) : (fun x => f x) a = f a := Eq.refl _ 335 | example (a : α) (b : β) : (a, b).1 = a := Eq.refl _ 336 | example : 2 + 3 = 5 := Eq.refl _ 337 | ``` 338 | 339 | 342 | 343 | 这个特性非常重要,以至于库中为 ``Eq.refl _`` 专门定义了一个符号 ``rfl``: 344 | 345 | ```lean 346 | # variable (α β : Type) 347 | example (f : α → β) (a : α) : (fun x => f x) a = f a := rfl 348 | example (a : α) (b : β) : (a, b).1 = a := rfl 349 | example : 2 + 3 = 5 := rfl 350 | ``` 351 | 352 | 360 | 361 | 然而,等价不仅仅是一种关系。它有一个重要的性质,即每个断言都遵从等价性,即我们可以在不改变真值的情况下对表达式做等价代换。也就是说,给定 ``h1 : a = b`` 和 ``h2 : p a``,我们可以构造一个证明 ``p b``,只需要使用代换 ``Eq.subst h1 h2``。 362 | 363 | ```lean 364 | example (α : Type) (a b : α) (p : α → Prop) 365 | (h1 : a = b) (h2 : p a) : p b := 366 | Eq.subst h1 h2 367 | 368 | example (α : Type) (a b : α) (p : α → Prop) 369 | (h1 : a = b) (h2 : p a) : p b := 370 | h1 ▸ h2 371 | ``` 372 | 373 | 384 | 385 | 第二个例子中的三角形是建立在 ``Eq.subst`` 和 ``Eq.symm`` 之上的宏,它可以通过 ``\t`` 来输入。 386 | 387 | 规则 ``Eq.subst`` 定义了一些辅助规则,用来执行更显式的替换。它们是为处理应用型项,即形式为 ``s t`` 的项而设计的。这些辅助规则是,使用 ``congrArg`` 来替换参数,使用 ``congrFun`` 来替换正在应用的项,并且可以同时使用 ``congr`` 来替换两者。 388 | 389 | ```lean 390 | variable (α : Type) 391 | variable (a b : α) 392 | variable (f g : α → Nat) 393 | variable (h₁ : a = b) 394 | variable (h₂ : f = g) 395 | 396 | example : f a = f b := congrArg f h₁ 397 | example : f a = g a := congrFun h₂ a 398 | example : f a = g b := congr h₂ h₁ 399 | ``` 400 | 401 | 404 | 405 | Lean 的库包含大量通用的等式,例如: 406 | 407 | ```lean 408 | variable (a b c : Nat) 409 | 410 | example : a + 0 = a := Nat.add_zero a 411 | example : 0 + a = a := Nat.zero_add a 412 | example : a * 1 = a := Nat.mul_one a 413 | example : 1 * a = a := Nat.one_mul a 414 | example : a + b = b + a := Nat.add_comm a b 415 | example : a + b + c = a + (b + c) := Nat.add_assoc a b c 416 | example : a * b = b * a := Nat.mul_comm a b 417 | example : a * b * c = a * (b * c) := Nat.mul_assoc a b c 418 | example : a * (b + c) = a * b + a * c := Nat.mul_add a b c 419 | example : a * (b + c) = a * b + a * c := Nat.left_distrib a b c 420 | example : (a + b) * c = a * c + b * c := Nat.add_mul a b c 421 | example : (a + b) * c = a * c + b * c := Nat.right_distrib a b c 422 | ``` 423 | 424 | 432 | 433 | ``Nat.mul_add`` 和 ``Nat.add_mul`` 是 ``Nat.left_distrib`` 和 ``Nat.right_distrib`` 的代称。上面的属性是为自然数类型 ``Nat`` 声明的。 434 | 435 | 这是一个使用代换以及结合律、交换律和分配律的自然数计算的例子。 436 | 437 | ```lean 438 | example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y := 439 | have h1 : (x + y) * (x + y) = (x + y) * x + (x + y) * y := 440 | Nat.mul_add (x + y) x y 441 | have h2 : (x + y) * (x + y) = x * x + y * x + (x * y + y * y) := 442 | (Nat.add_mul x y x) ▸ (Nat.add_mul x y y) ▸ h1 443 | h2.trans (Nat.add_assoc (x * x + y * x) (x * y) (y * y)).symm 444 | ``` 445 | 446 | 467 | 468 | 注意,``Eq.subst`` 的第二个隐式参数提供了将要发生代换的表达式上下文,其类型为 ``α → Prop``。因此,推断这个谓词需要一个*高阶合一*(higher-order unification)的实例。一般来说,确定高阶合一器是否存在的问题是无法确定的,而 Lean 充其量只能提供不完美的和近似的解决方案。因此,``Eq.subst`` 并不总是做你想要它做的事。宏 ``h ▸ e`` 使用了更有效的启发式方法来计算这个隐参数,并且在不能应用 ``Eq.subst`` 的情况下通常会成功。 469 | 470 | 因为等式推理是如此普遍和重要,Lean 提供了许多机制来更有效地执行它。下一节将提供允许你以更自然和清晰的方式编写计算式证明的语法。但更重要的是,等式推理是由项重写器、简化器和其他种类的自动化方法支持的。术语重写器和简化器将在下一节中简要描述,然后在下一章中更详细地描述。 471 | 472 | 476 | 477 | 计算式证明 478 | -------------------- 479 | 480 | 486 | 487 | 一个计算式证明是指一串使用诸如等式的传递性等基本规则得到的中间结果。在 Lean 中,计算式证明从关键字 ``calc`` 开始,语法如下: 488 | 489 | ``` 490 | calc 491 | _0 'op_1' _1 ':=' _1 492 | '_' 'op_2' _2 ':=' _2 493 | ... 494 | '_' 'op_n' _n ':=' _n 495 | ``` 496 | 497 | 504 | 505 | `calc` 下的每一行使用相同的缩进。每个 ``_i`` 是 ``_{i-1} op_i _i`` 的证明。 506 | 507 | 我们也可以在第一个关系中使用 `_`(就在 ``_0`` 之后),这对于对齐关系/证明对的序列很有用: 508 | 509 | ``` 510 | calc _0 511 | '_' 'op_1' _1 ':=' _1 512 | '_' 'op_2' _2 ':=' _2 513 | ... 514 | '_' 'op_n' _n ':=' _n 515 | ``` 516 | 517 | 520 | 521 | 例子: 522 | 523 | ```lean 524 | variable (a b c d e : Nat) 525 | variable (h1 : a = b) 526 | variable (h2 : b = c + 1) 527 | variable (h3 : c = d) 528 | variable (h4 : e = 1 + d) 529 | 530 | theorem T : a = e := 531 | calc 532 | a = b := h1 533 | _ = c + 1 := h2 534 | _ = d + 1 := congrArg Nat.succ h3 535 | _ = 1 + d := Nat.add_comm d 1 536 | _ = e := Eq.symm h4 537 | ``` 538 | 539 | 546 | 547 | 这种写证明的风格在与 `simp` 和 `rewrite` 策略(Tactic)结合使用时最为有效,这些策略将在下一章详细讨论。例如,用缩写 `rw` 表示重写,上面的证明可以写成如下。 548 | 549 | ```lean 550 | # variable (a b c d e : Nat) 551 | # variable (h1 : a = b) 552 | # variable (h2 : b = c + 1) 553 | # variable (h3 : c = d) 554 | # variable (h4 : e = 1 + d) 555 | theorem T : a = e := 556 | calc 557 | a = b := by rw [h1] 558 | _ = c + 1 := by rw [h2] 559 | _ = d + 1 := by rw [h3] 560 | _ = 1 + d := by rw [Nat.add_comm] 561 | _ = e := by rw [h4] 562 | ``` 563 | 564 | 573 | 574 | 本质上,``rw`` 策略使用一个给定的等式(它可以是一个假设、一个定理名称或一个复杂的项)来「重写」目标。如果这样做将目标简化为一种等式 ``t = t``,那么该策略将使用反身性来证明这一点。 575 | 576 | 重写可以一次应用一系列,因此上面的证明可以缩写为: 577 | 578 | ```lean 579 | # variable (a b c d e : Nat) 580 | # variable (h1 : a = b) 581 | # variable (h2 : b = c + 1) 582 | # variable (h3 : c = d) 583 | # variable (h4 : e = 1 + d) 584 | theorem T : a = e := 585 | calc 586 | a = d + 1 := by rw [h1, h2, h3] 587 | _ = 1 + d := by rw [Nat.add_comm] 588 | _ = e := by rw [h4] 589 | ``` 590 | 591 | 594 | 595 | 甚至更简单: 596 | 597 | ```lean 598 | # variable (a b c d e : Nat) 599 | # variable (h1 : a = b) 600 | # variable (h2 : b = c + 1) 601 | # variable (h3 : c = d) 602 | # variable (h4 : e = 1 + d) 603 | theorem T : a = e := 604 | by rw [h1, h2, h3, Nat.add_comm, h4] 605 | ``` 606 | 607 | 614 | 615 | 相反,``simp`` 策略通过在项中以任意顺序在任何适用的地方重复应用给定的等式来重写目标。它还使用了之前声明给系统的其他规则,并明智地应用交换性以避免循环。因此,我们也可以如下证明定理: 616 | 617 | ```lean 618 | # variable (a b c d e : Nat) 619 | # variable (h1 : a = b) 620 | # variable (h2 : b = c + 1) 621 | # variable (h3 : c = d) 622 | # variable (h4 : e = 1 + d) 623 | theorem T : a = e := 624 | by simp [h1, h2, h3, Nat.add_comm, h4] 625 | ``` 626 | 627 | 633 | 634 | 我们将在下一章讨论 ``rw`` 和 ``simp`` 的变体。 635 | 636 | ``calc`` 命令可以配置为任何支持某种形式的传递性的关系式。它甚至可以结合不同的关系式。 637 | 638 | ```lean 639 | example (a b c d : Nat) (h1 : a = b) (h2 : b ≤ c) (h3 : c + 1 < d) : a < d := 640 | calc 641 | a = b := h1 642 | _ < b + 1 := Nat.lt_succ_self b 643 | _ ≤ c + 1 := Nat.succ_le_succ h2 644 | _ < d := h3 645 | ``` 646 | 647 | 652 | 653 | 你可以通过添加 `Trans` 类型类(Type class)的新实例来「教给」`calc` 新的传递性定理。稍后将介绍类型类,但下面的小示例将演示如何使用新的 `Trans` 实例扩展 `calc` 表示法。 654 | 655 | ```lean 656 | def divides (x y : Nat) : Prop := 657 | ∃ k, k*x = y 658 | 659 | def divides_trans (h₁ : divides x y) (h₂ : divides y z) : divides x z := 660 | let ⟨k₁, d₁⟩ := h₁ 661 | let ⟨k₂, d₂⟩ := h₂ 662 | ⟨k₁ * k₂, by rw [Nat.mul_comm k₁ k₂, Nat.mul_assoc, d₁, d₂]⟩ 663 | 664 | def divides_mul (x : Nat) (k : Nat) : divides x (k*x) := 665 | ⟨k, rfl⟩ 666 | 667 | instance : Trans divides divides divides where 668 | trans := divides_trans 669 | 670 | example (h₁ : divides x y) (h₂ : y = z) : divides x (2*z) := 671 | calc 672 | divides x y := h₁ 673 | _ = z := h₂ 674 | divides _ (2*z) := divides_mul .. 675 | 676 | infix:50 " ∣ " => divides 677 | 678 | example (h₁ : divides x y) (h₂ : y = z) : divides x (2*z) := 679 | calc 680 | x ∣ y := h₁ 681 | _ = z := h₂ 682 | _ ∣ 2*z := divides_mul .. 683 | ``` 684 | 685 | 692 | 693 | 上面的例子也清楚地表明,即使关系式没有中缀符号,也可以使用 `calc`。最后,我们注意到上面例子中的竖线`∣`是unicode。我们使用 unicode 来确保我们不会重载在`match .. with`表达式中使用的ASCII`|`。 694 | 695 | 699 | 700 | 使用 ``calc``,我们可以以一种更自然、更清晰的方式写出上一节的证明。 701 | 702 | ```lean 703 | example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y := 704 | calc 705 | (x + y) * (x + y) = (x + y) * x + (x + y) * y := by rw [Nat.mul_add] 706 | _ = x * x + y * x + (x + y) * y := by rw [Nat.add_mul] 707 | _ = x * x + y * x + (x * y + y * y) := by rw [Nat.add_mul] 708 | _ = x * x + y * x + x * y + y * y := by rw [←Nat.add_assoc] 709 | ``` 710 | 711 | 716 | 717 | 这里值得考虑另一种 `calc` 表示法。当第一个表达式占用这么多空间时,在第一个关系中使用 `_` 自然会对齐所有关系式: 718 | 719 | ```lean 720 | example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y := 721 | calc (x + y) * (x + y) 722 | _ = (x + y) * x + (x + y) * y := by rw [Nat.mul_add] 723 | _ = x * x + y * x + (x + y) * y := by rw [Nat.add_mul] 724 | _ = x * x + y * x + (x * y + y * y) := by rw [Nat.add_mul] 725 | _ = x * x + y * x + x * y + y * y := by rw [←Nat.add_assoc] 726 | ``` 727 | 728 | 734 | 735 | ``Nat.add_assoc`` 之前的左箭头指挥重写以相反的方向使用等式。(你可以输入 ``\l`` 或 ascii 码 ``<-``。)如果追求简洁,``rw`` 和 ``simp`` 可以一次性完成这项工作: 736 | 737 | ```lean 738 | example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y := 739 | by rw [Nat.mul_add, Nat.add_mul, Nat.add_mul, ←Nat.add_assoc] 740 | 741 | example (x y : Nat) : (x + y) * (x + y) = x * x + y * x + x * y + y * y := 742 | by simp [Nat.mul_add, Nat.add_mul, Nat.add_assoc] 743 | ``` 744 | 745 | 749 | 750 | ## 存在量词 751 | 752 | 763 | 764 | 存在量词可以写成 ``exists x : α, p x`` 或 ``∃ x : α, p x``。这两个写法实际上在 Lean 的库中的定义为一个更冗长的表达式,``Exists (fun x : α => p x)``。 765 | 766 | 存在量词也有一个引入规则和一个消去规则。引入规则很简单:要证明 ``∃ x : α, p x``,只需提供一个合适的项 ``t`` 和对 ``p t`` 的证明即可。``∃`` 用 ``\exists`` 或简写 ``\ex`` 输入,下面是一些例子: 767 | 768 | ```lean 769 | example : ∃ x : Nat, x > 0 := 770 | have h : 1 > 0 := Nat.zero_lt_succ 0 771 | Exists.intro 1 h 772 | 773 | example (x : Nat) (h : x > 0) : ∃ y, y < x := 774 | Exists.intro 0 h 775 | 776 | example (x y z : Nat) (hxy : x < y) (hyz : y < z) : ∃ w, x < w ∧ w < z := 777 | Exists.intro y (And.intro hxy hyz) 778 | 779 | #check @Exists.intro -- ∀ {α : Sort u_1} {p : α → Prop} (w : α), p w → Exists p 780 | ``` 781 | 782 | 786 | 787 | 当类型可从上下文中推断时,我们可以使用匿名构造子表示法 ``⟨t, h⟩`` 替换 ``Exists.intro t h``。 788 | 789 | ```lean 790 | example : ∃ x : Nat, x > 0 := 791 | have h : 1 > 0 := Nat.zero_lt_succ 0 792 | ⟨1, h⟩ 793 | 794 | example (x : Nat) (h : x > 0) : ∃ y, y < x := 795 | ⟨0, h⟩ 796 | 797 | example (x y z : Nat) (hxy : x < y) (hyz : y < z) : ∃ w, x < w ∧ w < z := 798 | ⟨y, hxy, hyz⟩ 799 | ``` 800 | 801 | 812 | 813 | 注意 ``Exists.intro`` 有隐参数:Lean 必须在结论 ``∃ x, p x`` 中推断谓词 ``p : α → Prop``。这不是一件小事。例如,如果我们有 ``hg : g 0 0 = 0`` 和 ``Exists.intro 0 hg``,有许多可能的值的谓词 ``p``,对应定理 ``∃ x, g x x = x``,``∃ x, g x x = 0``,``∃ x, g x 0 = x``,等等。Lean 使用上下文来推断哪个是合适的。下面的例子说明了这一点,在这个例子中,我们设置了选项 ``pp.explicit`` 为true,要求 Lean 打印隐参数。 814 | 815 | 832 | 833 | ```lean 834 | variable (g : Nat → Nat → Nat) 835 | variable (hg : g 0 0 = 0) 836 | 837 | theorem gex1 : ∃ x, g x x = x := ⟨0, hg⟩ 838 | theorem gex2 : ∃ x, g x 0 = x := ⟨0, hg⟩ 839 | theorem gex3 : ∃ x, g 0 0 = x := ⟨0, hg⟩ 840 | theorem gex4 : ∃ x, g x x = 0 := ⟨0, hg⟩ 841 | 842 | set_option pp.explicit true -- 打印隐参数 843 | #print gex1 844 | #print gex2 845 | #print gex3 846 | #print gex4 847 | ``` 848 | 849 | 861 | 862 | 我们可以将 ``Exists.intro`` 视为信息隐藏操作,因为它将断言的具体实例隐藏起来变成了存在量词。存在消去规则 ``Exists.elim`` 执行相反的操作。它允许我们从 ``∃ x : α, p x`` 证明一个命题 ``q``,通过证明对于任意值 ``w`` 时 ``p w`` 都能推出 ``q``。粗略地说,既然我们知道有一个 ``x`` 满足 ``p x``,我们可以给它起个名字,比如 ``w``。如果 ``q`` 没有提到 ``w``,那么表明 ``p w`` 能推出 ``q`` 就等同于表明 ``q`` 从任何这样的 ``x`` 的存在而推得。下面是一个例子: 863 | 864 | ```lean 865 | variable (α : Type) (p q : α → Prop) 866 | 867 | example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x := 868 | Exists.elim h 869 | (fun w => 870 | fun hw : p w ∧ q w => 871 | show ∃ x, q x ∧ p x from ⟨w, hw.right, hw.left⟩) 872 | ``` 873 | 874 | 892 | 893 | 把存在消去规则和析取消去规则作个比较可能会带来一些启发。命题 ``∃ x : α, p x`` 可以看成一个对所有 ``α`` 中的元素 ``a`` 所组成的命题 ``p a`` 的大型析取。注意到匿名构造子 ``⟨w, hw.right, hw.left⟩`` 是嵌套的构造子 ``⟨w, ⟨hw.right, hw.left⟩⟩`` 的缩写。 894 | 895 | 存在式命题类型很像依值类型一节所述的 sigma 类型。给定 ``a : α`` 和 ``h : p a`` 时,项 ``Exists.intro a h`` 具有类型 ``(∃ x : α, p x) : Prop``,而 ``Sigma.mk a h`` 具有类型 ``(Σ x : α, p x) : Type``。``∃`` 和 ``Σ`` 之间的相似性是Curry-Howard同构的另一例子。 896 | 897 | Lean 提供一个更加方便的消去存在量词的途径,那就是通过 ``match`` 表达式。 898 | 899 | ```lean 900 | variable (α : Type) (p q : α → Prop) 901 | 902 | example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x := 903 | match h with 904 | | ⟨w, hw⟩ => ⟨w, hw.right, hw.left⟩ 905 | ``` 906 | 907 | 917 | 918 | ``match`` 表达式是 Lean 功能定义系统的一部分,它提供了复杂功能的方便且丰富的表达方式。再一次,正是Curry-Howard同构让我们能够采用这种机制来编写证明。``match`` 语句将存在断言「析构」到组件 ``w`` 和 ``hw`` 中,然后可以在语句体中使用它们来证明命题。我们可以对 match 中使用的类型进行注释,以提高清晰度: 919 | 920 | ```lean 921 | # variable (α : Type) (p q : α → Prop) 922 | example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x := 923 | match h with 924 | | ⟨(w : α), (hw : p w ∧ q w)⟩ => ⟨w, hw.right, hw.left⟩ 925 | ``` 926 | 927 | 930 | 931 | 我们甚至可以同时使用 match 语句来分解合取: 932 | 933 | ```lean 934 | # variable (α : Type) (p q : α → Prop) 935 | example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x := 936 | match h with 937 | | ⟨w, hpw, hqw⟩ => ⟨w, hqw, hpw⟩ 938 | ``` 939 | 940 | 943 | 944 | Lean 还提供了一个模式匹配的 ``let`` 表达式: 945 | 946 | ```lean 947 | # variable (α : Type) (p q : α → Prop) 948 | example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x := 949 | let ⟨w, hpw, hqw⟩ := h 950 | ⟨w, hqw, hpw⟩ 951 | ``` 952 | 953 | 958 | 959 | 这实际上是上面的 ``match`` 结构的替代表示法。Lean 甚至允许我们在 ``fun`` 表达式中使用隐含的 ``match``: 960 | 961 | 962 | ```lean 963 | # variable (α : Type) (p q : α → Prop) 964 | example : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := 965 | fun ⟨w, hpw, hqw⟩ => ⟨w, hqw, hpw⟩ 966 | ``` 967 | 968 | 975 | 976 | 我们将在[归纳和递归](./induction_and_recursion.md)一章看到所有这些变体都是更一般的模式匹配构造的实例。 977 | 978 | 在下面的例子中,我们将 ``even a`` 定义为 ``∃ b, a = 2 * b``,然后我们证明两个偶数的和是偶数。 979 | 980 | 981 | ```lean 982 | def is_even (a : Nat) := ∃ b, a = 2 * b 983 | 984 | theorem even_plus_even (h1 : is_even a) (h2 : is_even b) : is_even (a + b) := 985 | Exists.elim h1 (fun w1 (hw1 : a = 2 * w1) => 986 | Exists.elim h2 (fun w2 (hw2 : b = 2 * w2) => 987 | Exists.intro (w1 + w2) 988 | (calc a + b 989 | _ = 2 * w1 + 2 * w2 := by rw [hw1, hw2] 990 | _ = 2 * (w1 + w2) := by rw [Nat.mul_add]))) 991 | ``` 992 | 993 | 998 | 999 | 使用本章描述的各种小工具——``match`` 语句、匿名构造子和 ``rewrite`` 策略,我们可以简洁地写出如下证明: 1000 | 1001 | ```lean 1002 | # def is_even (a : Nat) := ∃ b, a = 2 * b 1003 | theorem even_plus_even (h1 : is_even a) (h2 : is_even b) : is_even (a + b) := 1004 | match h1, h2 with 1005 | | ⟨w1, hw1⟩, ⟨w2, hw2⟩ => ⟨w1 + w2, by rw [hw1, hw2, Nat.mul_add]⟩ 1006 | ``` 1007 | 1008 | 1016 | 1017 | 就像构造主义的「或」比古典的「或」强,同样,构造的「存在」也比古典的「存在」强。例如,下面的推论需要经典推理,因为从构造的角度来看,知道并不是每一个 ``x`` 都满足 ``¬ p``,并不等于有一个特定的 ``x`` 满足 ``p``。 1018 | 1019 | ```lean 1020 | open Classical 1021 | variable (p : α → Prop) 1022 | 1023 | example (h : ¬ ∀ x, ¬ p x) : ∃ x, p x := 1024 | byContradiction 1025 | (fun h1 : ¬ ∃ x, p x => 1026 | have h2 : ∀ x, ¬ p x := 1027 | fun x => 1028 | fun h3 : p x => 1029 | have h4 : ∃ x, p x := ⟨x, h3⟩ 1030 | show False from h1 h4 1031 | show False from h h2) 1032 | ``` 1033 | 1034 | 1040 | 1041 | 下面是一些涉及存在量词的常见等式。在下面的练习中,我们鼓励你尽可能多写出证明。你需要判断哪些是非构造主义的,因此需要一些经典推理。 1042 | 1043 | ```lean 1044 | open Classical 1045 | 1046 | variable (α : Type) (p q : α → Prop) 1047 | variable (r : Prop) 1048 | 1049 | example : (∃ x : α, r) → r := sorry 1050 | example (a : α) : r → (∃ x : α, r) := sorry 1051 | example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r := sorry 1052 | example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) := sorry 1053 | 1054 | example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) := sorry 1055 | example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) := sorry 1056 | example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) := sorry 1057 | example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) := sorry 1058 | 1059 | example : (∀ x, p x → r) ↔ (∃ x, p x) → r := sorry 1060 | example (a : α) : (∃ x, p x → r) ↔ (∀ x, p x) → r := sorry 1061 | example (a : α) : (∃ x, r → p x) ↔ (r → ∃ x, p x) := sorry 1062 | ``` 1063 | 1064 | 1070 | 1071 | 注意,第二个例子和最后两个例子要求假设至少有一个类型为 ``α`` 的元素 ``a``。 1072 | 1073 | 以下是两个比较困难的问题的解: 1074 | 1075 | ```lean 1076 | open Classical 1077 | 1078 | variable (α : Type) (p q : α → Prop) 1079 | variable (a : α) 1080 | variable (r : Prop) 1081 | 1082 | example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) := 1083 | Iff.intro 1084 | (fun ⟨a, (h1 : p a ∨ q a)⟩ => 1085 | Or.elim h1 1086 | (fun hpa : p a => Or.inl ⟨a, hpa⟩) 1087 | (fun hqa : q a => Or.inr ⟨a, hqa⟩)) 1088 | (fun h : (∃ x, p x) ∨ (∃ x, q x) => 1089 | Or.elim h 1090 | (fun ⟨a, hpa⟩ => ⟨a, (Or.inl hpa)⟩) 1091 | (fun ⟨a, hqa⟩ => ⟨a, (Or.inr hqa)⟩)) 1092 | 1093 | example : (∃ x, p x → r) ↔ (∀ x, p x) → r := 1094 | Iff.intro 1095 | (fun ⟨b, (hb : p b → r)⟩ => 1096 | fun h2 : ∀ x, p x => 1097 | show r from hb (h2 b)) 1098 | (fun h1 : (∀ x, p x) → r => 1099 | show ∃ x, p x → r from 1100 | byCases 1101 | (fun hap : ∀ x, p x => ⟨a, λ h' => h1 hap⟩) 1102 | (fun hnap : ¬ ∀ x, p x => 1103 | byContradiction 1104 | (fun hnex : ¬ ∃ x, p x → r => 1105 | have hap : ∀ x, p x := 1106 | fun x => 1107 | byContradiction 1108 | (fun hnp : ¬ p x => 1109 | have hex : ∃ x, p x → r := ⟨x, (fun hp => absurd hp hnp)⟩ 1110 | show False from hnex hex) 1111 | show False from hnap hap))) 1112 | ``` 1113 | 1114 | 1118 | 1119 | ## 多来点儿证明语法 1120 | 1121 | 1131 | 1132 | 我们已经看到像 ``fun``、``have`` 和 ``show`` 这样的关键字使得写出反映非正式数学证明结构的正式证明项成为可能。在本节中,我们将讨论证明语言的一些通常很方便的附加特性。 1133 | 1134 | 首先,我们可以使用匿名的 ``have`` 表达式来引入一个辅助目标,而不需要标注它。我们可以使用关键字 ``this`` 来引用最后一个以这种方式引入的表达式: 1135 | 1136 | ```lean 1137 | variable (f : Nat → Nat) 1138 | variable (h : ∀ x : Nat, f x ≤ f (x + 1)) 1139 | 1140 | example : f 0 ≤ f 3 := 1141 | have : f 0 ≤ f 1 := h 0 1142 | have : f 0 ≤ f 2 := Nat.le_trans this (h 1) 1143 | show f 0 ≤ f 3 from Nat.le_trans this (h 2) 1144 | ``` 1145 | 1146 | 1153 | 1154 | 通常证明从一个事实转移到另一个事实,所以这可以有效地消除杂乱的大量标签。 1155 | 1156 | 当目标可以推断出来时,我们也可以让 Lean 写 ``by assumption`` 来填写证明: 1157 | 1158 | ```lean 1159 | # variable (f : Nat → Nat) 1160 | # variable (h : ∀ x : Nat, f x ≤ f (x + 1)) 1161 | example : f 0 ≤ f 3 := 1162 | have : f 0 ≤ f 1 := h 0 1163 | have : f 0 ≤ f 2 := Nat.le_trans (by assumption) (h 1) 1164 | show f 0 ≤ f 3 from Nat.le_trans (by assumption) (h 2) 1165 | ``` 1166 | 1167 | 1180 | 1181 | 这告诉 Lean 使用 ``assumption`` 策略,反过来,通过在局部上下文中找到合适的假设来证明目标。我们将在下一章学习更多关于 ``assumption`` 策略的内容。 1182 | 1183 | 我们也可以通过写 ``‹p›`` 的形式要求 Lean 填写证明,其中 ``p`` 是我们希望 Lean 在上下文中找到的证明命题。你可以分别使用 ``\f<`` 和 ``\f>`` 输入这些角引号。字母「f」表示「French」,因为 unicode 符号也可以用作法语引号。事实上,这个符号在 Lean 中定义如下: 1184 | 1185 | ```lean 1186 | notation "‹" p "›" => show p by assumption 1187 | ``` 1188 | 1189 | 1195 | 1196 | 这种方法比使用 ``by assumption`` 更稳健,因为需要推断的假设类型是显式给出的。它还使证明更具可读性。这里有一个更详细的例子: 1197 | 1198 | ```lean 1199 | variable (f : Nat → Nat) 1200 | variable (h : ∀ x : Nat, f x ≤ f (x + 1)) 1201 | 1202 | example : f 0 ≥ f 1 → f 1 ≥ f 2 → f 0 = f 2 := 1203 | fun _ : f 0 ≥ f 1 => 1204 | fun _ : f 1 ≥ f 2 => 1205 | have : f 0 ≥ f 2 := Nat.le_trans ‹f 1 ≥ f 2› ‹f 0 ≥ f 1› 1206 | have : f 0 ≤ f 2 := Nat.le_trans (h 0) (h 1) 1207 | show f 0 = f 2 from Nat.le_antisymm this ‹f 0 ≥ f 2› 1208 | ``` 1209 | 1210 | 1216 | 1217 | 你可以这样使用法语引号来指代上下文中的「任何东西」,而不仅仅是匿名引入的东西。它的使用也不局限于命题,尽管将它用于数据有点奇怪: 1218 | 1219 | ```lean 1220 | example (n : Nat) : Nat := ‹Nat› 1221 | ``` 1222 | 1223 | 1226 | 1227 | 稍后,我们将展示如何使用 Lean 中的宏系统扩展证明语言。 1228 | 1229 | 1233 | 1234 | 练习 1235 | --------- 1236 | 1237 | 1240 | 1241 | 1. 证明以下等式: 1242 | 1243 | ```lean 1244 | variable (α : Type) (p q : α → Prop) 1245 | 1246 | example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) := sorry 1247 | example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) := sorry 1248 | example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x := sorry 1249 | ``` 1250 | 1251 | 1254 | 1255 | 你还应该想想为什么在最后一个例子中反过来是不能证明的。 1256 | 1257 | 1263 | 1264 | 2. 当一个公式的组成部分不依赖于被全称的变量时,通常可以把它提取出一个全称量词的范围。尝试证明这些(第二个命题中的一个方向需要经典逻辑): 1265 | 1266 | ```lean 1267 | variable (α : Type) (p q : α → Prop) 1268 | variable (r : Prop) 1269 | 1270 | example : α → ((∀ x : α, r) ↔ r) := sorry 1271 | example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r := sorry 1272 | example : (∀ x, r → p x) ↔ (r → ∀ x, p x) := sorry 1273 | ``` 1274 | 1275 | 1280 | 1281 | 3. 考虑「理发师悖论」:在一个小镇里,这里有一个(男性)理发师给所有不为自己刮胡子的人刮胡子。证明这里存在矛盾: 1282 | 1283 | ```lean 1284 | variable (men : Type) (barber : men) 1285 | variable (shaves : men → men → Prop) 1286 | 1287 | example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : False := sorry 1288 | ``` 1289 | 1290 | 1301 | 1302 | 4. 如果没有任何参数,类型 ``Prop`` 的表达式只是一个断言。填入下面 ``prime`` 和 ``Fermat_prime`` 的定义,并构造每个给定的断言。例如,通过断言每个自然数 ``n`` 都有一个大于 ``n`` 的质数,你可以说有无限多个质数。哥德巴赫弱猜想指出,每一个大于5的奇数都是三个素数的和。如果有必要,请查阅费马素数的定义或其他任何资料。 1303 | 1304 | ```lean 1305 | def even (n : Nat) : Prop := sorry 1306 | 1307 | def prime (n : Nat) : Prop := sorry 1308 | 1309 | def infinitely_many_primes : Prop := sorry 1310 | 1311 | def Fermat_prime (n : Nat) : Prop := sorry 1312 | 1313 | def infinitely_many_Fermat_primes : Prop := sorry 1314 | 1315 | def goldbach_conjecture : Prop := sorry 1316 | 1317 | def Goldbach's_weak_conjecture : Prop := sorry 1318 | 1319 | def Fermat's_last_theorem : Prop := sorry 1320 | ``` 1321 | 1322 | 1326 | 1327 | 5. 尽可能多地证明存在量词一节列出的等式。 1328 | -------------------------------------------------------------------------------- /structures_and_records.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 结构体和记录 7 | ====================== 8 | 9 | 36 | 37 | 我们已经看到Lean 的基本系统包括归纳类型。此外,显然仅基于类型宇宙、依赖箭头类型和归纳类型,就有可能构建一个坚实的数学大厦;其他的一切都是由此而来。Lean 标准库包含许多归纳类型的实例(例如,``Nat``,``Prod``,``List``),甚至逻辑连接词也是使用归纳类型定义的。 38 | 39 | 回忆一下,只包含一个构造子的非递归归纳类型被称为 **结构体(structure)** 或 **记录(record)** 。乘积类型是一种结构体,依值乘积(Sigma)类型也是如此。一般来说,每当我们定义一个结构体 ``S`` 时,我们通常定义*投影*(projection)函数来「析构」(destruct)``S`` 的每个实例并检索存储在其字段中的值。``prod.pr1`` 和 ``prod.pr2``,分别返回乘积对中的第一个和第二个元素的函数,就是这种投影的例子。 40 | 41 | 在编写程序或形式化数学时,定义包含许多字段的结构体是很常见的。Lean 中可用 ``structure`` 命令实现此过程。当我们使用这个命令定义一个结构体时,Lean 会自动生成所有的投影函数。``structure`` 命令还允许我们根据以前定义的结构体定义新的结构体。此外,Lean 为定义给定结构体的实例提供了方便的符号。 42 | 43 | 47 | 48 | 声明结构体 49 | -------------------- 50 | 51 | 56 | 57 | 结构体命令本质上是定义归纳数据类型的「前端」。每个 ``structure`` 声明都会引入一个同名的命名空间。一般形式如下: 58 | 59 | ``` 60 | structure where 61 | :: 62 | ``` 63 | 64 | 67 | 68 | 大多数部分不是必要的。例子: 69 | 70 | ```lean 71 | structure Point (α : Type u) where 72 | mk :: (x : α) (y : α) 73 | ``` 74 | 75 | 83 | 84 | 类型 ``Point`` 的值是使用 ``Point.mk a b`` 创建的,并且点 ``p`` 的字段可以使用 ``Point.x p`` 和 ``Point.y p``。结构体命令还生成有用的递归器和定理。下面是为上述声明生成的一些结构体方法。 85 | 86 | 97 | 98 | ```lean 99 | # structure Point (α : Type u) where 100 | # mk :: (x : α) (y : α) 101 | #check Point -- 类型 102 | #check @Point.rec -- 消去器(eliminator) 103 | #check @Point.mk -- 构造子 104 | #check @Point.x -- 投影 105 | #check @Point.y -- 投影 106 | ``` 107 | 108 | 113 | 114 | 119 | 120 | 如果没有提供构造子名称,则默认的构造函数名为 ``mk``。如果在每个字段之间添加换行符,也可以避免字段名周围的括号。 121 | 122 | ```lean 123 | structure Point (α : Type u) where 124 | x : α 125 | y : α 126 | ``` 127 | 128 | 133 | 134 | 下面是一些使用生成的结构的简单定理和表达式。像往常一样,您可以通过使用命令 ``open Point`` 来避免前缀 ``Point``。 135 | 136 | ```lean 137 | # structure Point (α : Type u) where 138 | # x : α 139 | # y : α 140 | #eval Point.x (Point.mk 10 20) 141 | #eval Point.y (Point.mk 10 20) 142 | 143 | open Point 144 | 145 | example (a b : α) : x (mk a b) = a := 146 | rfl 147 | 148 | example (a b : α) : y (mk a b) = b := 149 | rfl 150 | ``` 151 | 152 | 157 | 158 | 给定 ``p : Point Nat``,符号 ``p.x`` 是 ``Point.x p`` 的缩写。这提供了一种方便的方式来访问结构体的字段。 159 | 160 | ```lean 161 | # structure Point (α : Type u) where 162 | # x : α 163 | # y : α 164 | def p := Point.mk 10 20 165 | 166 | #check p.x -- Nat 167 | #eval p.x -- 10 168 | #eval p.y -- 20 169 | ``` 170 | 171 | 180 | 181 | 点记号不仅方便于访问记录的投影,而且也方便于应用同名命名空间中定义的函数。回想一下[合取](./propositions_and_proofs.md#_conjunction)一节,如果 ``p`` 具有 ``Point`` 类型,那么表达式 ``p.foo`` 被解释为 ``Point.foo p``,假设 ``foo`` 的第一个非隐式参数具有类型 ``Point``,表达式 ``p.add q`` 因此是 ``Point.add p q`` 的缩写。可见下面的例子。 182 | 183 | ```lean 184 | structure Point (α : Type u) where 185 | x : α 186 | y : α 187 | deriving Repr 188 | 189 | def Point.add (p q : Point Nat) := 190 | mk (p.x + q.x) (p.y + q.y) 191 | 192 | def p : Point Nat := Point.mk 1 2 193 | def q : Point Nat := Point.mk 3 4 194 | 195 | #eval p.add q -- {x := 4, y := 6} 196 | ``` 197 | 198 | 209 | 210 | 在下一章中,您将学习如何定义一个像 ``add`` 这样的函数,这样它就可以通用地为 ``Point α`` 的元素工作,而不仅仅是 ``Point Nat``,只要假设 ``α`` 有一个关联的加法操作。 211 | 212 | 更一般地,给定一个表达式 ``p.foo x y z`` 其中`p : Point`,Lean 会把 ``p`` 以 ``Point`` 为类型插入到 ``Point.foo`` 的第一个参数。例如,下面是标量乘法的定义,``p.smul 3`` 被解释为 ``Point.smul 3 p``。 213 | 214 | ```lean 215 | # structure Point (α : Type u) where 216 | # x : α 217 | # y : α 218 | # deriving Repr 219 | def Point.smul (n : Nat) (p : Point Nat) := 220 | Point.mk (n * p.x) (n * p.y) 221 | 222 | def p : Point Nat := Point.mk 1 2 223 | 224 | #eval p.smul 3 -- {x := 3, y := 6} 225 | ``` 226 | 227 | 231 | 232 | 对 ``List.map`` 函数使用类似的技巧很常用。它接受一个列表作为它的第二个非隐式参数: 233 | 234 | ```lean 235 | #check @List.map 236 | 237 | def xs : List Nat := [1, 2, 3] 238 | def f : Nat → Nat := fun x => x * x 239 | 240 | #eval xs.map f -- [1, 4, 9] 241 | ``` 242 | 243 | 246 | 247 | 此处 ``xs.map f`` 被解释为 ``List.map f xs``。 248 | 249 | 253 | 254 | 对象 255 | ------- 256 | 257 | 264 | 265 | 我们一直在使用构造子创建结构体类型的元素。对于包含许多字段的结构,这通常是不方便的,因为我们必须记住字段定义的顺序。因此,Lean 为定义结构体类型的元素提供了以下替代符号。 266 | 267 | ``` 268 | { ( := )* : structure-type } 269 | or 270 | { ( := )* } 271 | ``` 272 | 273 | 280 | 281 | 只要可以从期望的类型推断出结构体的名称,后缀 ``: structure-type`` 就可以省略。例如,我们使用这种表示法来定义「Point」。字段的指定顺序无关紧要,因此下面的所有表达式定义相同的Point。 282 | 283 | ```lean 284 | structure Point (α : Type u) where 285 | x : α 286 | y : α 287 | 288 | #check { x := 10, y := 20 : Point Nat } -- Point ℕ 289 | #check { y := 20, x := 10 : Point _ } 290 | #check ({ x := 10, y := 20 } : Point Nat) 291 | 292 | example : Point Nat := 293 | { y := 20, x := 10 } 294 | ``` 295 | 296 | 301 | 302 | 如果一个字段的值没有指定,Lean 会尝试推断它。如果不能推断出未指定的字段,Lean 会标记一个错误,表明相应的占位符无法合成。 303 | 304 | ```lean 305 | structure MyStruct where 306 | {α : Type u} 307 | {β : Type v} 308 | a : α 309 | b : β 310 | 311 | #check { a := 10, b := true : MyStruct } 312 | ``` 313 | 314 | 325 | 326 | **记录更新(Record update)** 是另一个常见的操作,相当于通过修改旧记录中的一个或多个字段的值来创建一个新的记录对象。通过在字段赋值之前添加注释 ``s with``,Lean 允许您指定记录规范中未赋值的字段,该字段应从之前定义的结构对象 ``s`` 中获取。如果提供了多个记录对象,那么将按顺序访问它们,直到Lean 找到一个包含未指定字段的记录对象。如果在访问了所有对象之后仍未指定任何字段名,Lean 将引发错误。 327 | 328 | ```lean 329 | structure Point (α : Type u) where 330 | x : α 331 | y : α 332 | deriving Repr 333 | 334 | def p : Point Nat := 335 | { x := 1, y := 2 } 336 | 337 | #eval { p with y := 3 } -- { x := 1, y := 3 } 338 | #eval { p with x := 4 } -- { x := 4, y := 2 } 339 | 340 | structure Point3 (α : Type u) where 341 | x : α 342 | y : α 343 | z : α 344 | 345 | def q : Point3 Nat := 346 | { x := 5, y := 5, z := 5 } 347 | 348 | def r : Point3 Nat := 349 | { p, q with x := 6 } 350 | 351 | example : r.x = 6 := rfl 352 | example : r.y = 2 := rfl 353 | example : r.z = 5 := rfl 354 | ``` 355 | 356 | 360 | 361 | 继承 362 | ----------- 363 | 364 | 368 | 369 | 我们可以通过添加新的字段来 **扩展** 现有的结构体。这个特性允许我们模拟一种形式的 **继承** 。 370 | 371 | ```lean 372 | structure Point (α : Type u) where 373 | x : α 374 | y : α 375 | 376 | inductive Color where 377 | | red | green | blue 378 | 379 | structure ColorPoint (α : Type u) extends Point α where 380 | c : Color 381 | ``` 382 | 383 | 387 | 388 | 在下一个例子中,我们使用多重继承定义一个结构体,然后使用父结构的对象定义一个对象。 389 | 390 | ```lean 391 | structure Point (α : Type u) where 392 | x : α 393 | y : α 394 | z : α 395 | 396 | structure RGBValue where 397 | red : Nat 398 | green : Nat 399 | blue : Nat 400 | 401 | structure RedGreenPoint (α : Type u) extends Point α, RGBValue where 402 | no_blue : blue = 0 403 | 404 | def p : Point Nat := 405 | { x := 10, y := 10, z := 20 } 406 | 407 | def rgp : RedGreenPoint Nat := 408 | { p with red := 200, green := 40, blue := 0, no_blue := rfl } 409 | 410 | example : rgp.x = 10 := rfl 411 | example : rgp.red = 200 := rfl 412 | ``` 413 | -------------------------------------------------------------------------------- /title_page.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Lean 4 定理证明 6 | 7 | 作者:*Jeremy Avigad, Leonardo de Moura, Soonho Kong and Sebastian Ullrich, 以及来自 Lean 社区的贡献者* 8 | 9 | **[Lean-zh 项目组](https://github.com/Lean-zh) 译** 10 | 11 | 18 | 19 | 本书假定你使用 Lean 4。安装方式参见 [Lean 4 手册](https://www.leanprover.cn/lean4/doc/) 20 | 中的 [快速开始](https://www.leanprover.cn/lean4/doc/quickstart.html) 一节。 21 | 本书的第一版为 Lean 2 编写,Lean 3 版请访问 [此处](https://leanprover.github.io/theorem_proving_in_lean/)。 22 | -------------------------------------------------------------------------------- /unixode.sty: -------------------------------------------------------------------------------- 1 | % ------------------------------------------------------------------------------ 2 | % (C) 2012-2013 Olivier Verdier 3 | % Unixode Package 4 | % XeTeX Unicode character definitions 5 | % ------------------------------------------------------------------------------ 6 | \NeedsTeXFormat{LaTeX2e} 7 | \ProvidesPackage{unixode}[2012/05/10] 8 | 9 | \RequirePackage{ifxetex} 10 | 11 | \ifxetex 12 | %\RequirePackage{mathspec} 13 | %\RequirePackage{fontspec} 14 | \defaultfontfeatures{Ligatures=TeX} 15 | \usepackage{newunicodechar} 16 | \newunicodechar{α}{\ensuremath{\mathrm{\alpha}}} 17 | \newunicodechar{β}{\ensuremath{\mathrm{\beta}}} 18 | \newunicodechar{γ}{\ensuremath{\mathrm{\gamma}}} 19 | \newunicodechar{δ}{\ensuremath{\mathrm{\delta}}} 20 | \newunicodechar{ε}{\ensuremath{\mathrm{\varepsilon}}} 21 | \newunicodechar{ζ}{\ensuremath{\mathrm{\zeta}}} 22 | \newunicodechar{η}{\ensuremath{\mathrm{\eta}}} 23 | \newunicodechar{θ}{\ensuremath{\mathrm{\theta}}} 24 | \newunicodechar{ι}{\ensuremath{\mathrm{\iota}}} 25 | \newunicodechar{κ}{\ensuremath{\mathrm{\kappa}}} 26 | \newunicodechar{λ}{\ensuremath{\mathrm{\lambda}}} 27 | \newunicodechar{μ}{\ensuremath{\mathrm{\mu}}} 28 | \newunicodechar{ν}{\ensuremath{\mathrm{\nu}}} 29 | \newunicodechar{ξ}{\ensuremath{\mathrm{\xi}}} 30 | \newunicodechar{π}{\ensuremath{\mathrm{\mathnormal{\pi}}}} 31 | \newunicodechar{ρ}{\ensuremath{\mathrm{\rho}}} 32 | \newunicodechar{σ}{\ensuremath{\mathrm{\sigma}}} 33 | \newunicodechar{τ}{\ensuremath{\mathrm{\tau}}} 34 | \newunicodechar{φ}{\ensuremath{\mathrm{\varphi}}} 35 | \newunicodechar{χ}{\ensuremath{\mathrm{\chi}}} 36 | \newunicodechar{ψ}{\ensuremath{\mathrm{\psi}}} 37 | \newunicodechar{ω}{\ensuremath{\mathrm{\omega}}} 38 | 39 | \newunicodechar{Γ}{\ensuremath{\mathrm{\Gamma}}} 40 | \newunicodechar{Δ}{\ensuremath{\mathrm{\Delta}}} 41 | \newunicodechar{Θ}{\ensuremath{\mathrm{\Theta}}} 42 | \newunicodechar{Λ}{\ensuremath{\mathrm{\Lambda}}} 43 | \newunicodechar{Σ}{\ensuremath{\Sigma}} 44 | \newunicodechar{Φ}{\ensuremath{\mathrm{\Phi}}} 45 | \newunicodechar{Ξ}{\ensuremath{\mathrm{\Xi}}} 46 | \newunicodechar{Ψ}{\ensuremath{\mathrm{\Psi}}} 47 | \newunicodechar{Ω}{\ensuremath{\mathrm{\Omega}}} 48 | 49 | \newunicodechar{ℵ}{\ensuremath{\aleph}} 50 | 51 | \newunicodechar{≤}{\ensuremath{\leq}} 52 | \newunicodechar{≥}{\ensuremath{\geq}} 53 | \newunicodechar{≠}{\ensuremath{\neq}} 54 | \newunicodechar{≈}{\ensuremath{\approx}} 55 | \newunicodechar{≡}{\ensuremath{\equiv}} 56 | \newunicodechar{≃}{\ensuremath{\simeq}} 57 | \newunicodechar{≺}{\ensuremath{\prec}} 58 | \newunicodechar{≼}{\ensuremath{\preceq}} 59 | 60 | \newunicodechar{≤}{\ensuremath{\leq}} 61 | \newunicodechar{≥}{\ensuremath{\geq}} 62 | 63 | \newunicodechar{∂}{\ensuremath{\partial}} 64 | \newunicodechar{∆}{\ensuremath{\triangle}} % or \laplace? 65 | 66 | \newunicodechar{∫}{\ensuremath{\int}} 67 | \newunicodechar{∑}{\ensuremath{\mathrm{\Sigma}}} 68 | \newunicodechar{Π}{\ensuremath{\Pi}} 69 | 70 | \newunicodechar{⊥}{\ensuremath{\perp}} 71 | \newunicodechar{∞}{\ensuremath{\infty}} 72 | \newunicodechar{∂}{\ensuremath{\partial}} 73 | 74 | \newunicodechar{∓}{\ensuremath{\mp}} 75 | \newunicodechar{±}{\ensuremath{\pm}} 76 | \newunicodechar{×}{\ensuremath{\times}} 77 | 78 | \newunicodechar{⊕}{\ensuremath{\oplus}} 79 | \newunicodechar{⊗}{\ensuremath{\otimes}} 80 | \newunicodechar{⊞}{\ensuremath{\boxplus}} 81 | 82 | \newunicodechar{∇}{\ensuremath{\nabla}} 83 | \newunicodechar{√}{\ensuremath{\sqrt}} 84 | 85 | \newunicodechar{⬝}{\ensuremath{\cdot}} 86 | \newunicodechar{•}{\ensuremath{\cdot}} 87 | \newunicodechar{∘}{\ensuremath{\circ}} 88 | 89 | \newunicodechar{⁻}{\ensuremath{^{\textup{\kern1pt\rule{2pt}{0.3pt}\kern-1pt}}}} 90 | \newunicodechar{▸}{\ensuremath{\blacktriangleright}} 91 | 92 | \newunicodechar{∧}{\ensuremath{\wedge}} 93 | \newunicodechar{∨}{\ensuremath{\vee}} 94 | \newunicodechar{¬}{\ensuremath{\neg}} 95 | \newunicodechar{⊢}{\ensuremath{\vdash}} 96 | 97 | %\newunicodechar{⟨}{\ensuremath{\left\langle}} 98 | %\newunicodechar{⟩}{\ensuremath{\right\rangle}} 99 | \newunicodechar{⟨}{\ensuremath{\langle}} 100 | \newunicodechar{⟩}{\ensuremath{\rangle}} 101 | 102 | \newunicodechar{∀}{\ensuremath{\forall}} 103 | \newunicodechar{∃}{\ensuremath{\exists}} 104 | 105 | \newunicodechar{↦}{\ensuremath{\mapsto}} 106 | \newunicodechar{→}{\ensuremath{\rightarrow}} 107 | \newunicodechar{↔}{\ensuremath{\leftrightarrow}} 108 | \newunicodechar{⇒}{\ensuremath{\Rightarrow}} 109 | \newunicodechar{⟹}{\ensuremath{\Longrightarrow}} 110 | \newunicodechar{⇐}{\ensuremath{\Leftarrow}} 111 | \newunicodechar{⟸}{\ensuremath{\Longleftarrow}} 112 | 113 | \newunicodechar{∩}{\ensuremath{\cap}} 114 | \newunicodechar{∪}{\ensuremath{\cup}} 115 | \newunicodechar{⊂}{\ensuremath{\subseteq}} 116 | \newunicodechar{⊆}{\ensuremath{\subseteq}} 117 | \newunicodechar{⊄}{\ensuremath{\nsubseteq}} 118 | \newunicodechar{⊈}{\ensuremath{\nsubseteq}} 119 | \newunicodechar{⊃}{\ensuremath{\supseteq}} 120 | \newunicodechar{⊇}{\ensuremath{\supseteq}} 121 | \newunicodechar{⊅}{\ensuremath{\nsupseteq}} 122 | \newunicodechar{⊉}{\ensuremath{\nsupseteq}} 123 | \newunicodechar{∈}{\ensuremath{\in}} 124 | \newunicodechar{∉}{\ensuremath{\notin}} 125 | \newunicodechar{∋}{\ensuremath{\ni}} 126 | \newunicodechar{∌}{\ensuremath{\notni}} 127 | \newunicodechar{∅}{\ensuremath{\emptyset}} 128 | 129 | \newunicodechar{∖}{\ensuremath{\setminus}} 130 | \newunicodechar{†}{\ensuremath{\dag}} 131 | 132 | \newunicodechar{ℕ}{\ensuremath{\mathbb{N}}} 133 | \newunicodechar{ℤ}{\ensuremath{\mathbb{Z}}} 134 | \newunicodechar{ℝ}{\ensuremath{\mathbb{R}}} 135 | \newunicodechar{ℚ}{\ensuremath{\mathbb{Q}}} 136 | \newunicodechar{ℂ}{\ensuremath{\mathbb{C}}} 137 | \newunicodechar{⌞}{\ensuremath{\llcorner}} 138 | \newunicodechar{⌟}{\ensuremath{\lrcorner}} 139 | \newunicodechar{⦃}{\ensuremath{\{\!|}} 140 | \newunicodechar{⦄}{\ensuremath{|\!\}}} 141 | \newunicodechar{∣}{\ensuremath{\mid}} 142 | \newunicodechar{∥}{\ensuremath{\parallel}} 143 | 144 | \newunicodechar{₁}{\ensuremath{_1}} 145 | \newunicodechar{₂}{\ensuremath{_2}} 146 | \newunicodechar{₃}{\ensuremath{_3}} 147 | \newunicodechar{₄}{\ensuremath{_4}} 148 | \newunicodechar{₅}{\ensuremath{_5}} 149 | \newunicodechar{₆}{\ensuremath{_6}} 150 | \newunicodechar{₇}{\ensuremath{_7}} 151 | \newunicodechar{₈}{\ensuremath{_8}} 152 | \newunicodechar{₉}{\ensuremath{_9}} 153 | \newunicodechar{₀}{\ensuremath{_0}} 154 | \newunicodechar{ᵢ}{\ensuremath{_i}} 155 | \newunicodechar{ⱼ}{\ensuremath{_j}} 156 | \newunicodechar{ₘ}{\ensuremath{_m}} 157 | \newunicodechar{ₙ}{\ensuremath{_n}} 158 | \newunicodechar{ᵤ}{\ensuremath{_u}} 159 | \newunicodechar{↑}{\ensuremath{\uparrow}} 160 | \newunicodechar{↓}{\ensuremath{\downarrow}} 161 | \else 162 | \usepackage[utf8x]{inputenc} 163 | \SetUnicodeOption{mathletters} 164 | \DeclareUnicodeCharacter{952}{\ensuremath{\theta}} 165 | \fi 166 | --------------------------------------------------------------------------------