From 3b160e9337708519a8b6db770c743210c7593024 Mon Sep 17 00:00:00 2001 From: Seasawher Date: Sun, 18 Aug 2024 17:01:58 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E6=BC=94=E7=BF=92=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=81=A8=E8=A7=A3=E7=AD=94=E3=81=AE=E5=90=8C=E6=9C=9F=E3=81=8C?= =?UTF-8?q?=E4=BF=9D=E3=81=9F=E3=82=8C=E3=81=AA=E3=81=84=20Fixes=20#641?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sync.yml | 66 ++++++++++++++++++++++++++++++++++++++ lakefile.lean | 14 +++++--- 2 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/sync.yml diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 00000000..cd88de1b --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,66 @@ +name: sync exercise and solutions + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + branches: + - main + paths: + - 'Examples/Exercise/**' + - 'Examples/Solution/**' + - '.github/workflows/sync.yml' + - 'lakefile.lean' + - 'lake-manifest.json' + push: + branches: + - main + paths: + - 'Examples/Exercise/**' + - 'Examples/Solution/**' + - '.github/workflows/sync.yml' + - 'lakefile.lean' + - 'lake-manifest.json' + workflow_dispatch: + +permissions: + contents: write + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Install elan + run: | + curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none + echo "$HOME/.elan/bin" >> $GITHUB_PATH + + # ファイル名変更により解答と対応しない演習問題が残ることを避けるため、 + # いったん演習ファイルを全て削除している + - name: run mk_exercise + run: | + rm -r Examples/Exercise + lake run mk_exercise + + - name: detect diff + id: diff + run: | + git add -N Examples/Exercise + git diff --name-only --exit-code + continue-on-error: true + + - name: commit + if: steps.diff.outcome == 'failure' + run: | + git config user.name "GitHub Action" + git config user.email "action@github.com" + git add Examples/Exercise + git commit -m "generated by GitHub Action" + git push diff --git a/lakefile.lean b/lakefile.lean index 32f8a584..bf054642 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -30,12 +30,16 @@ def runCmd (cmd : String) (args : Array String) : ScriptM Bool := do IO.eprint out.stderr return hasError -script build do +/-- mk_exercise を実行し、演習問題の解答に +解答部分を sorry に置き換えるなどの処理を施して演習問題ファイルを生成する。-/ +script mk_exercise do if ← runCmd "lake" #["exe", "mk_exercise", "Examples/Solution", "Examples/Exercise"] then return 1 - if ← runCmd "lake" #["exe", "mdgen", "Examples", "src"] then return 1 - if ← runCmd "mdbook" #["build"] then return 1 return 0 -script link_test do - if ← runCmd "lychee" #["--base", "book", "."] then return 1 +/-- mk_exercise と mdgen と mdbook を順に実行し、 +Lean ファイルから Markdown ファイルと HTML ファイルを生成する。-/ +script build do + if ← runCmd "lake" #["run", "mk_exercise"] then return 1 + if ← runCmd "lake" #["exe", "mdgen", "Examples", "src"] then return 1 + if ← runCmd "mdbook" #["build"] then return 1 return 0 From ec20b4860e5180bc111bffd035e1ce707b8ebe19 Mon Sep 17 00:00:00 2001 From: Seasawher Date: Sun, 18 Aug 2024 18:42:12 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=81=AB=20Solution=20=E5=81=B4=E3=81=AE?= =?UTF-8?q?=E3=81=BF=E5=A4=89=E6=9B=B4=E3=82=92=E3=82=B3=E3=83=9F=E3=83=83?= =?UTF-8?q?=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Examples/Solution/BarberParadox.lean | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Examples/Solution/BarberParadox.lean b/Examples/Solution/BarberParadox.lean index e406f125..7ee73ea9 100644 --- a/Examples/Solution/BarberParadox.lean +++ b/Examples/Solution/BarberParadox.lean @@ -7,11 +7,13 @@ その内容は自然言語では次のように説明できます。 -> ある村に床屋がいます。その床屋は、自分で髭を剃る村人の髭は剃らず、自分で髭を剃らない村人の髭を剃るという信念を持っています。このとき、その床屋は自分の髭を剃るでしょうか? -> -> 仮に剃るとしたら、その床屋はまさに「自分で自分の髭を剃っている」ので剃ってはならないはずで、矛盾が生じます。逆に剃らないとすると、その床屋はまさに「自分で自分の髭を剃らない」ので自分の髭を剃らなければならないことになり、矛盾が生じます。 -> -> いずれにせよ矛盾が生じてしまいます。 +```admonish info title="" +ある村に床屋がいます。その床屋は、自分で髭を剃る村人の髭は剃らず、自分で髭を剃らない村人の髭を剃るという信念を持っています。このとき、その床屋は自分の髭を剃るでしょうか? + +仮に剃るとしたら、その床屋はまさに「自分で自分の髭を剃っている」ので剃ってはならないはずで、矛盾が生じます。逆に剃らないとすると、その床屋はまさに「自分で自分の髭を剃らない」ので自分の髭を剃らなければならないことになり、矛盾が生じます。 + +いずれにせよ矛盾が生じてしまうので、そんな床屋は存在しません! +``` この内容を Lean で表現してみましょう。演習問題として、`sorry` の部分を埋めてみてください。 -/ From cba9336f6088f4f639c1fa69afa5952a2826dc6a Mon Sep 17 00:00:00 2001 From: Seasawher Date: Sun, 18 Aug 2024 18:44:44 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=81=AE=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=82=B3=E3=83=9F=E3=83=83?= =?UTF-8?q?=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Examples/Exercise/BarberParadox.lean | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Examples/Exercise/BarberParadox.lean b/Examples/Exercise/BarberParadox.lean index 60203004..0dd1428a 100644 --- a/Examples/Exercise/BarberParadox.lean +++ b/Examples/Exercise/BarberParadox.lean @@ -7,11 +7,13 @@ その内容は自然言語では次のように説明できます。 -> ある村に床屋がいます。その床屋は、自分で髭を剃る村人の髭は剃らず、自分で髭を剃らない村人の髭を剃るという信念を持っています。このとき、その床屋は自分の髭を剃るでしょうか? -> -> 仮に剃るとしたら、その床屋はまさに「自分で自分の髭を剃っている」ので剃ってはならないはずで、矛盾が生じます。逆に剃らないとすると、その床屋はまさに「自分で自分の髭を剃らない」ので自分の髭を剃らなければならないことになり、矛盾が生じます。 -> -> いずれにせよ矛盾が生じてしまいます。 +```admonish info title="" +ある村に床屋がいます。その床屋は、自分で髭を剃る村人の髭は剃らず、自分で髭を剃らない村人の髭を剃るという信念を持っています。このとき、その床屋は自分の髭を剃るでしょうか? + +仮に剃るとしたら、その床屋はまさに「自分で自分の髭を剃っている」ので剃ってはならないはずで、矛盾が生じます。逆に剃らないとすると、その床屋はまさに「自分で自分の髭を剃らない」ので自分の髭を剃らなければならないことになり、矛盾が生じます。 + +いずれにせよ矛盾が生じてしまうので、そんな床屋は存在しません! +``` この内容を Lean で表現してみましょう。演習問題として、`sorry` の部分を埋めてみてください。 -/ From 82231872bfaf322a4f620b5c53d92aee98c6dc2e Mon Sep 17 00:00:00 2001 From: Seasawher Date: Sun, 18 Aug 2024 19:21:13 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E5=A4=A9=E4=BD=BF=E3=81=A8=E6=82=AA?= =?UTF-8?q?=E9=AD=94=E3=81=AE=E8=AB=96=E7=90=86=E3=83=91=E3=82=BA=E3=83=AB?= =?UTF-8?q?=20resolve=20#601?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Examples/Exercise/KnightAndKnave.lean | 118 +++++++++++++++++++ Examples/Solution/KnightAndKnave.lean | 158 ++++++++++++++++++++++++++ src/SUMMARY.md | 1 + 3 files changed, 277 insertions(+) create mode 100644 Examples/Exercise/KnightAndKnave.lean create mode 100644 Examples/Solution/KnightAndKnave.lean diff --git a/Examples/Exercise/KnightAndKnave.lean b/Examples/Exercise/KnightAndKnave.lean new file mode 100644 index 00000000..4154b1bd --- /dev/null +++ b/Examples/Exercise/KnightAndKnave.lean @@ -0,0 +1,118 @@ +/- # 騎士と悪党の論理パズル + +騎士(Knight)と悪党(Knave)の論理パズルは、著名な論理学者 Raymond Smullyan によって考案されたとされています。それ以来様々な変種が考案され続けてきました。ここでは、その中でも基本的なものを Lean で形式化し、解いていきます。 + +## 問題の内容 + +[Critical thinking web](https://philosophy.hku.hk/think/logic/knights.php) から問題を引用します。まずは自然言語で問題文を述べましょう。 + +```admonish info title="" +ある島には、騎士と悪党しか住んでいません。すべての島民は騎士であるか悪党であるかのいずれかです。騎士が話すことは常に真であり、悪党が話すことは常に偽です。 + +あなたはゾーイとメルという2人の島民に出会いました。 +ゾーイは「メルは悪党です」と言いました。 +メルは「ゾーイも私も悪党ではない」と言いました。 +さて、ゾーイとメルはそれぞれ騎士か悪党のどちらでしょうか? +``` + +## 問題の形式化 + +さっそく形式化を行っていきます。まず、ある島があってそこには騎士と悪党しか住んでいないというところですが、これは `Islander` という型に `knight` と `knave` という部分集合が存在するというように形式化できます。そのうえで、島民が騎士か悪党のどちらかであることを `axiom` コマンドで仮定しましょう。 + +ここでは後で便利なように `simp` 補題も示しておくことにします。 +-/ + +/-- その島の住民を表す型 -/ +opaque Islander : Type + +/-- Islander の部分集合として定義した騎士の全体 -/ +opaque knight : Islander → Prop + +/-- Islander の部分集合として定義した悪党の全体 -/ +opaque knave : Islander → Prop + +/-- 人は騎士か悪党のどちらか -/ +axiom knight_or_knave (p : Islander) : knight p ∨ knave p + +/-- 人が騎士かつ悪党であることはない -/ +axiom knight_ne_knave (p : Islander) : ¬(knight p ∧ knave p) + +/-- 騎士でないことと悪党であることは同値 -/ +@[simp] theorem of_not_knight {p : Islander} : ¬ knight p ↔ knave p := by + have or := knight_or_knave p + have ne := knight_ne_knave p + constructor + all_goals + intro h + simp_all + +/-- 悪党でないことと騎士であることは同値 -/ +@[simp] theorem of_not_knave {p : Islander} : ¬ knave p ↔ knight p := by + have or := knight_or_knave p + have ne := knight_ne_knave p + constructor + all_goals + intro h + simp_all + +/- +```admonish warning title="注意" +`Islander` 型を `knight` と `knave` の2つのコンストラクタを持つ帰納型として定義すると、島民が2人しかいないことになってしまって、正しい表現にならないことに注意して下さい。 +``` +-/ + +/- これでゾーイとメルが島民であり、騎士か悪党かのどちらかであるということは次のように表現できます。-/ + +/-- ゾーイ -/ +axiom zoey : Islander + +/-- メル -/ +axiom mel : Islander + +/- 正直者であるとか嘘つきであるということを表現するために、誰がなんと発言したかを表現するための関数を用意します。その上で、それぞれの発言を愚直に形式化していきます。 -/ + +/-- p さんが body という命題を話したという事実を表す命題 -/ +opaque Islander.say (p : Islander) (body : Prop) : Prop + +variable {p : Islander} {body : Prop} + +/-- 騎士の発言内容はすべて真実 -/ +axiom statement_of_knight (h : knight p) (say : p.say body) : body + +/-- 悪党の発言内容はすべて偽 -/ +axiom statement_of_knave (h : knave p) (say : p.say body) : ¬body + +/-- ゾーイは「メルは悪党だ」と言った -/ +axiom zoey_says : zoey.say (knave mel) + +/-- メルは「ゾーイもメルも悪党ではない」と言った -/ +axiom mel_says : mel.say (¬ knave zoey ∧ ¬ knave mel) + +/- ここまでで問題文の前提部分の形式化は完了です。 + +残っているのは、「ゾーイとメルはそれぞれ騎士か悪党のどちらですか」と問う部分です。これは少し難しいです。 + +考えてみると、Lean で(証明すべき命題がわかっているときに)何かを証明するのはよくありますが、与えられた前提から何が言えるのかを明確なゴールなしに組み立てていくのはあまり見ないということにお気づきになるでしょう。この問題も、もし問いの内容が「ゾーイが騎士であることを示せ」とか「ゾーイが悪党であることを示せ」だったならば、今までの準備の下で簡単に形式化ができますが、「騎士なのか悪党なのか決定せよ」なので少し複雑になります。 + +ここでの解決方法は、`Solution` という型クラスを `inductive class` コマンドで作成し、そのインスタンスを作ってくださいという形式にすることです。 +-/ + +/-- `p` が騎士か悪党のどちらなのか知っていることを表す型クラス -/ +class inductive Solution (p : Islander) : Type where + | isKnight : knight p → Solution p + | isKnave : knave p → Solution p + +/- `class inductive` というものをあまり見たことがないかもしれませんが、これは普通の型クラスと異なりコンストラクタが複数ある型クラスを定義することができます。この `Solution` の場合、`p : Islander` に対してインスタンスを作成するときに、インスタンスが `isKnight` から来るのか `isKnave` から来るのか明示しなければなりません。そしてそのために、それぞれ `p` が騎士であるか悪党であるかのどちらかの証明が引数に要求されることになります。 + +## 問題文 + +以上の準備の下で、問題は次のように表現できます。以下の `sorry` の部分を埋めてください。 +-/ + +-- ゾーイは騎士か悪党か? +noncomputable instance : Solution zoey := by + sorry + +-- メルは騎士か悪党か? +noncomputable instance : Solution mel := by + sorry diff --git a/Examples/Solution/KnightAndKnave.lean b/Examples/Solution/KnightAndKnave.lean new file mode 100644 index 00000000..f10d08b7 --- /dev/null +++ b/Examples/Solution/KnightAndKnave.lean @@ -0,0 +1,158 @@ +/- # 騎士と悪党の論理パズル + +騎士(Knight)と悪党(Knave)の論理パズルは、著名な論理学者 Raymond Smullyan によって考案されたとされています。それ以来様々な変種が考案され続けてきました。ここでは、その中でも基本的なものを Lean で形式化し、解いていきます。 + +## 問題の内容 + +[Critical thinking web](https://philosophy.hku.hk/think/logic/knights.php) から問題を引用します。まずは自然言語で問題文を述べましょう。 + +```admonish info title="" +ある島には、騎士と悪党しか住んでいません。すべての島民は騎士であるか悪党であるかのいずれかです。騎士が話すことは常に真であり、悪党が話すことは常に偽です。 + +あなたはゾーイとメルという2人の島民に出会いました。 +ゾーイは「メルは悪党です」と言いました。 +メルは「ゾーイも私も悪党ではない」と言いました。 +さて、ゾーイとメルはそれぞれ騎士か悪党のどちらでしょうか? +``` + +## 問題の形式化 + +さっそく形式化を行っていきます。まず、ある島があってそこには騎士と悪党しか住んでいないというところですが、これは `Islander` という型に `knight` と `knave` という部分集合が存在するというように形式化できます。そのうえで、島民が騎士か悪党のどちらかであることを `axiom` コマンドで仮定しましょう。 + +ここでは後で便利なように `simp` 補題も示しておくことにします。 +-/ + +/-- その島の住民を表す型 -/ +opaque Islander : Type + +/-- Islander の部分集合として定義した騎士の全体 -/ +opaque knight : Islander → Prop + +/-- Islander の部分集合として定義した悪党の全体 -/ +opaque knave : Islander → Prop + +/-- 人は騎士か悪党のどちらか -/ +axiom knight_or_knave (p : Islander) : knight p ∨ knave p + +/-- 人が騎士かつ悪党であることはない -/ +axiom knight_ne_knave (p : Islander) : ¬(knight p ∧ knave p) + +/-- 騎士でないことと悪党であることは同値 -/ +@[simp] theorem of_not_knight {p : Islander} : ¬ knight p ↔ knave p := by + have or := knight_or_knave p + have ne := knight_ne_knave p + constructor + all_goals + intro h + simp_all + +/-- 悪党でないことと騎士であることは同値 -/ +@[simp] theorem of_not_knave {p : Islander} : ¬ knave p ↔ knight p := by + have or := knight_or_knave p + have ne := knight_ne_knave p + constructor + all_goals + intro h + simp_all + +/- +```admonish warning title="注意" +`Islander` 型を `knight` と `knave` の2つのコンストラクタを持つ帰納型として定義すると、島民が2人しかいないことになってしまって、正しい表現にならないことに注意して下さい。 +``` +-/ + +/- これでゾーイとメルが島民であり、騎士か悪党かのどちらかであるということは次のように表現できます。-/ + +/-- ゾーイ -/ +axiom zoey : Islander + +/-- メル -/ +axiom mel : Islander + +/- 正直者であるとか嘘つきであるということを表現するために、誰がなんと発言したかを表現するための関数を用意します。その上で、それぞれの発言を愚直に形式化していきます。 -/ + +/-- p さんが body という命題を話したという事実を表す命題 -/ +opaque Islander.say (p : Islander) (body : Prop) : Prop + +variable {p : Islander} {body : Prop} + +/-- 騎士の発言内容はすべて真実 -/ +axiom statement_of_knight (h : knight p) (say : p.say body) : body + +/-- 悪党の発言内容はすべて偽 -/ +axiom statement_of_knave (h : knave p) (say : p.say body) : ¬body + +/-- ゾーイは「メルは悪党だ」と言った -/ +axiom zoey_says : zoey.say (knave mel) + +/-- メルは「ゾーイもメルも悪党ではない」と言った -/ +axiom mel_says : mel.say (¬ knave zoey ∧ ¬ knave mel) + +/- ここまでで問題文の前提部分の形式化は完了です。 + +残っているのは、「ゾーイとメルはそれぞれ騎士か悪党のどちらですか」と問う部分です。これは少し難しいです。 + +考えてみると、Lean で(証明すべき命題がわかっているときに)何かを証明するのはよくありますが、与えられた前提から何が言えるのかを明確なゴールなしに組み立てていくのはあまり見ないということにお気づきになるでしょう。この問題も、もし問いの内容が「ゾーイが騎士であることを示せ」とか「ゾーイが悪党であることを示せ」だったならば、今までの準備の下で簡単に形式化ができますが、「騎士なのか悪党なのか決定せよ」なので少し複雑になります。 + +ここでの解決方法は、`Solution` という型クラスを `inductive class` コマンドで作成し、そのインスタンスを作ってくださいという形式にすることです。 +-/ + +/-- `p` が騎士か悪党のどちらなのか知っていることを表す型クラス -/ +class inductive Solution (p : Islander) : Type where + | isKnight : knight p → Solution p + | isKnave : knave p → Solution p + +/- `class inductive` というものをあまり見たことがないかもしれませんが、これは普通の型クラスと異なりコンストラクタが複数ある型クラスを定義することができます。この `Solution` の場合、`p : Islander` に対してインスタンスを作成するときに、インスタンスが `isKnight` から来るのか `isKnave` から来るのか明示しなければなりません。そしてそのために、それぞれ `p` が騎士であるか悪党であるかのどちらかの証明が引数に要求されることになります。 + +## 問題文 + +以上の準備の下で、問題は次のように表現できます。以下の `sorry` の部分を埋めてください。 +-/ +--##-- +theorem zoey_is_not_knave : ¬ knave zoey := by + -- ゾーイが悪党だと仮定する + intro h + + have mel_is_knight : knight mel := by + -- ゾーイの発言は偽なので、メルは悪党ではない + have := statement_of_knave h zoey_says + + -- よってメルは騎士 + simpa [of_not_knave] using this + + -- メルが騎士なので、メルの発言は真 + have mel_says_truth := statement_of_knight mel_is_knight mel_says + + -- したがってゾーイは騎士 + have : knight zoey := by + simp only [of_not_knave] at mel_says_truth + simp_all + + -- これは仮定に矛盾 + simp_all [knight_ne_knave] + +/-- ゾーイは騎士である -/ +theorem zoey_is_knight : knight zoey := by + have := zoey_is_not_knave + simpa using this + +/-- メルは悪党 -/ +theorem mel_is_knave : knave mel := by + -- ゾーイは騎士なのでゾーイの発言は真であり、 + -- よってメルは悪党 + exact statement_of_knight zoey_is_knight zoey_says +--##-- + +-- ゾーイは騎士か悪党か? +noncomputable instance : Solution zoey := by + -- sorry + apply Solution.isKnight + exact zoey_is_knight + -- sorry + +-- メルは騎士か悪党か? +noncomputable instance : Solution mel := by + -- sorry + apply Solution.isKnave + exact mel_is_knave + -- sorry diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 49302759..e0bf26dd 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -163,3 +163,4 @@ - [床屋のパラドクス](./Exercise/BarberParadox.md) - [De Morganの法則と排中律](./Exercise/DeMorganAndEm.md) - [含意の定義の正当性](./Exercise/ImpDefValidness.md) + - [騎士と悪党のパズル](./Exercise/KnightAndKnave.md)