From 5b6a9ba0ae9faa2ad70d80fa7504dcb9d170e29c Mon Sep 17 00:00:00 2001 From: Seasawher Date: Fri, 3 Jan 2025 06:47:23 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E6=9A=97=E9=BB=99=E3=81=AE=E5=BC=95?= =?UTF-8?q?=E6=95=B0=20`{=20x=20y=20:=20A=20}`=20=E3=82=92=E7=B4=B9?= =?UTF-8?q?=E4=BB=8B=E3=81=99=E3=82=8B=20Fixes=20#276?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LeanByExample/Parser/ImplicitBinder.lean | 86 ++++++++++++++++++++++++ booksrc/SUMMARY.md | 3 + 2 files changed, 89 insertions(+) create mode 100644 LeanByExample/Parser/ImplicitBinder.lean diff --git a/LeanByExample/Parser/ImplicitBinder.lean b/LeanByExample/Parser/ImplicitBinder.lean new file mode 100644 index 00000000..a821adcc --- /dev/null +++ b/LeanByExample/Parser/ImplicitBinder.lean @@ -0,0 +1,86 @@ +/- # 暗黙の引数 + +暗黙の引数とは、[`def`](#{root}/Declarative/Def.md) コマンドや [`theorem`](#{root}/Declarative/Theorem.md) コマンド、[`variable`](#{root}/Declarative/Variable.md) コマンドなどが受け取る構文の一つで、関数や定理の引数をユーザが明示的に与えなくても、Lean が文脈を読んで推論してくれるようになります。波括弧 `{}` で囲んで、`{x y : A}` のように書きます。 + +## 典型的な使用例 + +たとえば、暗黙の引数を使わなかった場合にどうなるかを見てみましょう。次の関数 `List.subs` は型パラメータ `α : Type` を受け取っていますが、第二引数 `xs : List α` を見れば `α : Type` が何であるかは分かるので、`List.subs` を使用する際に毎回 `α` を指定するのは冗長だと考えられます。 +-/ +import Lean --# + +/-- 与えられたリストの部分リストを全て返す(明示的引数バージョン) -/ +def List.subsₑ (α : Type) (xs : List α) : List (List α) := + match xs with + | [] => [[]] + | x :: xs => + let xss := subsₑ α xs + xss ++ xss.map (x :: ·) + +-- 型引数 α を明示的に与える必要がある +#eval List.subsₑ Nat [1, 2] + +-- 型引数を与えないと(当然ながら)エラーになってしまう +#guard_msgs (drop warning) in --# +#check_failure List.subs [1, 2] + +/- 引数 `α` を暗黙の引数として受け取るように変更すれば、Lean が `α : Type` の内容を推論してくれるようになり、`α` を省略できるようになります。-/ + +/-- 与えられたリストの部分リストを全て返す(暗黙引数バージョン) -/ +def List.subsᵢ {α : Type} (xs : List α) : List (List α) := + match xs with + | [] => [[]] + | x :: xs => + let xss := subsᵢ xs + xss ++ xss.map (x :: ·) + +-- 型引数を省略できるようになった +#eval List.subsᵢ [1, 2] + +-- 逆に今度は型引数を与えるとエラーになる +#guard_msgs (drop warning) in --# +#check_failure List.subsᵢ Nat [1, 2] + +/- ## 明示引数モード + +暗黙の引数を受け取るものとして定義された関数や定理に対して、`@` 記号を先頭に付けると全ての暗黙の引数が明示的引数に変化します。 +-/ + +-- 2 つの暗黙引数を持つ関数 +def List.map' {α β : Type} (f : α → β) : List α → List β + | [] => [] + | x :: xs => f x :: map' f xs + +-- 普通は次のように使う +#check List.map' (fun x => x == 1) [1, 2, 3] + +-- `@` 記号を付けると全ての引数が明示的引数に変化 +#check @List.map' Nat Bool (fun x => x == 1) [1, 2, 3] + +/- ## 構文についての補足 + +`Lean.Parser.Term.implicitBinder` というパーサが暗黙引数の構文に対応しています。-/ + +open Lean Elab Command in + +/-- ドキュメントコメントを取得して表示するコマンド -/ +elab "#doc " x:ident : command => do + let name ← liftCoreM do realizeGlobalConstNoOverload x + if let some s ← findDocString? (← getEnv) name then + logInfo m!"{s}" + +/-- +info: Implicit binder, like `{x y : A}` or `{x y}`. +In regular applications, whenever all parameters before it have been specified, +then a `_` placeholder is automatically inserted for this parameter. +Implicit parameters should be able to be determined from the other arguments and the return type +by unification. + +In `@` explicit mode, implicit binders behave like explicit binders. +-/ +#guard_msgs in + #doc Lean.Parser.Term.implicitBinder + +/- 上記のドキュメントコメントにも書かれていますが、構文としては暗黙の引数に型を指定しないことも許されます。 -/ + +-- `x : α` と書いたので、`α` が何かの型であることは分かる +def myId {α} (x : α) := x diff --git a/booksrc/SUMMARY.md b/booksrc/SUMMARY.md index 0cdbd432..9437acb5 100644 --- a/booksrc/SUMMARY.md +++ b/booksrc/SUMMARY.md @@ -65,6 +65,9 @@ - [protected: フルネームを強制する](./Modifier/Protected.md) - [scoped: 有効範囲を名前空間で限定](./Modifier/Scoped.md) +- [構文](./Parser/README.md) + - [`{x y : A}`: 暗黙の引数](./Parser/ImplicitBinder.md) + - [属性](./Attribute/README.md) - [app_unexpander: 関数適用の表示](./Attribute/AppUnexpander.md) - [cases_eliminator: 独自場合分け](./Attribute/CasesEliminator.md) From 4caf5c2a84c319ac217a81bfa4138c589e19e2d2 Mon Sep 17 00:00:00 2001 From: Seasawher Date: Fri, 3 Jan 2025 06:48:58 +0900 Subject: [PATCH 2/4] =?UTF-8?q?README=20=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LeanByExample/Parser/README.lean | 1 + 1 file changed, 1 insertion(+) create mode 100644 LeanByExample/Parser/README.lean diff --git a/LeanByExample/Parser/README.lean b/LeanByExample/Parser/README.lean new file mode 100644 index 00000000..9c728062 --- /dev/null +++ b/LeanByExample/Parser/README.lean @@ -0,0 +1 @@ +/- # 構文 -/ From 772adb129a66a625ec3de5c9d7d269f403ee8402 Mon Sep 17 00:00:00 2001 From: Seasawher Date: Fri, 3 Jan 2025 07:08:39 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E8=A6=8B=E5=87=BA=E3=81=97=E3=81=AB=20id?= =?UTF-8?q?=20=E3=82=92=E6=8C=AF=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LeanByExample/Declarative/Def.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LeanByExample/Declarative/Def.lean b/LeanByExample/Declarative/Def.lean index 2bbd31d9..9e927c6d 100644 --- a/LeanByExample/Declarative/Def.lean +++ b/LeanByExample/Declarative/Def.lean @@ -22,7 +22,7 @@ def add (n m : Nat) : Nat := n + m def threeAdd (n m l : Nat) : Nat := n + m + l -/- ## where 句 +/- ## where 句 { #Where } `where` 句を使うと、定義をする前に変数を使用することができます。主に、ヘルパー関数を宣言するために使用されます。 -/ @@ -51,7 +51,7 @@ where else loop (d + 1) (target / (d ^ m)) ((d, m) :: acc) -/- ## termination_by 句 +/- ## termination_by 句 { #TerminationBy } `termination_by` 句は、再帰関数が有限回の再帰で停止することを Lean にわかってもらうために、「再帰のたびに減少する指標」を指定します。 -/ -- 何も指定しないと、停止することが Lean にはわからないのでエラーになる From 24ba879d37aa57ba3f4038bc4f1da66adb20c035 Mon Sep 17 00:00:00 2001 From: Seasawher Date: Fri, 3 Jan 2025 07:41:57 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Curry=20Howard=20=E5=90=8C=E5=9E=8B?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=81=AE=E8=A9=B1=E3=82=92=20Prop=20?= =?UTF-8?q?=E3=81=AE=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AB=E7=A7=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LeanByExample/Declarative/Example.lean | 11 +--- LeanByExample/Diagnostic/Check.lean | 85 ++++++-------------------- LeanByExample/Type/Prop.lean | 24 +++++++- 3 files changed, 44 insertions(+), 76 deletions(-) diff --git a/LeanByExample/Declarative/Example.lean b/LeanByExample/Declarative/Example.lean index 33e119e8..4d61a590 100644 --- a/LeanByExample/Declarative/Example.lean +++ b/LeanByExample/Declarative/Example.lean @@ -1,19 +1,12 @@ /- # example -`example` は名前を付けずに命題の証明をすることができるコマンドです。 - -より正確には、`example : T := t` は `t` が型 `T` を持っていることを確かめます。特に `T` の型が `Prop` であるときには、最初に述べた通り `t` は `T` の証明だとみなすことができます。 --/ +`example` は名前を付けずに命題の証明をすることができるコマンドです。 -/ -- `1 + 1 = 2` は `rfl` で証明できる example : 1 + 1 = 2 := rfl --- `rfl` という項の型が `1 + 1 = 2` であると言っても同じこと -#check (rfl : 1 + 1 = 2) - -- `n + 0 = n` は `rfl` で証明できる example {n : Nat} : n + 0 = n := rfl --- `{n : Nat} → n + 0 = n` という依存関数型の項を `rfl` で構成できる、と言い換えられる -#check (rfl : {n : Nat} → n + 0 = n) +/- より正確には、`example : T := t` は `t` が型 `T` を持っていることを確かめます。特に `T` の型が [`Prop`](#{root}/Type/Prop.md) であるときには、最初に述べた通り `t` は `T` の証明だとみなすことができます。 -/ -- `[1, 2, 3]` は `List Nat` 型の項 example : List Nat := [1, 2, 3] diff --git a/LeanByExample/Diagnostic/Check.lean b/LeanByExample/Diagnostic/Check.lean index 11cdf66b..81001ced 100644 --- a/LeanByExample/Diagnostic/Check.lean +++ b/LeanByExample/Diagnostic/Check.lean @@ -1,97 +1,52 @@ /- # \#check -`#check` コマンドは、項の型を表示します。Lean ではすべての項に型があるので、どんな項にも使えます。`#check term` というコマンドで、`term` の型を表示します。逆に `term` の型が `T` であることを確かめるには [`example`](../Declarative/Example.md) コマンドを使用して `example : T := term` とします。 +`#check` コマンドは、項の型を表示します。Lean ではすべての項に型があるので、どんな項にも使えます。`#check term` というコマンドで、`term` の型を表示します。-/ -## 基本的な項の型 -/ - --- 文字 +-- 文字 `Char` #check 'a' --- 文字列 +-- 文字列 String #check "Hello" --- 自然数 +-- 自然数 Nat #check 1 --- 浮動小数点数 +-- 浮動小数点数 Float #check 1.0 --- 整数 +-- 整数 Int #check -2 -- `1` はそのままだと自然数扱いになるが、整数にキャストできる #check (1 : Int) -- 自然数のリスト -#check [1, 2, 3] +#check ([1, 2, 3] : List Nat) -- 自然数の配列 -#check #[1, 2, 3] +#check (#[1, 2, 3] : Array Nat) -- 関数 -#check fun x ↦ x + 42 +#check (fun x ↦ x + 42 : Nat → Nat) --- Bool, 真偽値 +-- 真偽値 Bool #check true --- 命題 +-- 命題 Prop #check True -/- ## 型の型 -「すべての」項には型があるので、型も型を持ちます。 --/ --- 宇宙変数を宣言する -universe u - -#check (Prop : Type) - --- Lean では `List` は Type を受け取って、Type を返す関数。 -#check (List : (α : Type u) → Type u) +/- 逆に `term` の型が `T` であることを確かめるには [`example`](../Declarative/Example.md) コマンドを使用して `example : T := term` とします。 -/ --- 配列も同じ。型を型に変換する関数。 -#check (Array : (α : Type u) → Type u) +example : Nat := 42 --- `Type` 自身も型を持っており… -#check (Type : Type 1) - -#check (Type 1 : Type 2) - --- この調子で限りなく続く -#check (Type u : Type (u + 1)) - -/- ## 命題と証明 -Lean では命題やその証明にも型があります。命題の型は `Prop` で、命題の項が証明になっています。逆に言えば、Lean ではある命題 `P : Prop` を証明するということは、`P` という型を持つ項 `proof : P` を構成することと同じです。 --/ +example : Int := - 13 -#check (1 + 1 = 2 : Prop) - --- `1 + 1 = 2` の証明 `two_eq_add_one` を構成 -theorem two_eq_add_one : 2 = 1 + 1 := by rfl - --- 証明の型が命題になっている -#check (two_eq_add_one : 2 = 1 + 1) - -/- ## 型としての True/False -`True` は型としては一点集合であり、 -`False` は型としては空集合です。 --/ - --- `trivial : True` は `True` 型を持つ項 -#check trivial - -/- ## 関数としての証明 -命題 `P → Q` の証明は、`P → Q` という型を持つ項です。 -つまり、`P` の証明 `h : P` に対して `Q` の証明を返す関数です。 +/- ## 型の型 +「すべての」項には型があるので、型も型を持ちます。多くの組み込み型の型は [`Type`](#{root}/Type/Type.md) になっています。 -/ -theorem tautology : True -> True := fun a ↦ a - --- 暗黙の変数を明示的にするために `@` を付けた。 --- `tautology : True → True` と出力されるので、 --- `tautology` は `True → True` という関数であることがわかる -#check (@tautology : True → True) +-- 文字列型の型は Type +#check (String : Type) --- 実際に `trivial : True` を「代入」すると --- `tautology trivial : True` となり、 --- 確かに `True` 型の項が得られていることがわかる。 -#check (tautology trivial : True) +-- 自然数型の型は Type +#check (Nat : Type) diff --git a/LeanByExample/Type/Prop.lean b/LeanByExample/Type/Prop.lean index dc671c9e..f5a34163 100644 --- a/LeanByExample/Type/Prop.lean +++ b/LeanByExample/Type/Prop.lean @@ -1,14 +1,34 @@ /- # Prop `Prop` は、命題全体がなす型宇宙です。 -命題とは、直観的には「曖昧さなしに真か偽かが定まっているような文章」のことです。`1 + 1 = 2` は命題です。`1 + 1 = 3` も(偽ではありますが)命題です。 +命題とは、直観的には「曖昧さなしに真か偽かが定まっているような文章」のことです。たとえば `1 + 1 = 2` は命題です。`1 + 1 = 3` も(偽ではありますが)命題です。 -/ #check (1 + 1 = 2 : Prop) #check (1 + 1 = 3 : Prop) +/- ## Curry Howard 同型対応 + +Lean では、各命題 `P : Prop` は再び型になっています。これは通常の数学では考えないことなので慣れないとわかりにくいのですが、言い換えれば命題 `P` の項というものを考えることができます。 + +たとえば、`1 + 1 = 2` という命題に対して、その項を構成することができます。 +-/ + +-- `1 + 1 = 2` という型を持つ項を、`rfl` で構成できる +#check (rfl : 1 + 1 = 2) + +-- 項に名前を付けた +def one_and_one_eq_two : 1 + 1 = 2 := rfl + +/- 実は、いま構成した `1 + 1 = 2` という型の項は `1 + 1 = 2` の証明になっています。実際のところ、命題 `P : Prop` の証明とは、Lean においては項 `h : P` そのものです。このことを強調するために、**証明項**という呼び方をすることもあります。-/ + +-- さっき構成した証明項で証明ができる +example : 1 + 1 = 2 := one_and_one_eq_two + +/- このように命題を型として、証明を項として実装できるのは、そもそも直観主義論理と型付きラムダ計算の間に **Curry Howard 同型対応**が存在するからです。簡単に言えば、論理と計算(プログラム)は出自は全く異なるものの何故か同じ構造を持っており、同じものであると見なせるということです。-/ + /- ## 命題論理 -`Bool` には真と偽に対応する `true` と `false` という項がありますが、`Prop` では真偽は `True` と `False` で表されます。 +[`Bool`](#{root}/Type/Bool.md) には真と偽に対応する `true` と `false` という項がありますが、`Prop` では真偽は `True` と `False` で表されます。 `P Q : Prop` があるとき、次のようにして新しい命題を得ることができます。