Software Engineering Lecture s04
Menu
Agda
Agda は Curry Howard 対応にそった定理証明支援システム。これを使った簡単な証明を学ぶ。
Agda の install の方法
Mac 上では Homebrew を使うのが良いそうです。
brew install agda/opt/homebrew/Cellar/agda/2.6.2.2/lib/agda/standard-library.agda-lib とかに入るので
echo standard-library > ~/.agda/defaults echo /opt/homebrew/Cellar/agda/2.6.2.2/lib/agda/standard-library.agda-lib > ~/.agda/libraries
VSCode で使う
Emacs を使う場合
brew tap caskroom/cask brew cask install emacsなどになるので、~/.emacs.d/init.el に以下のファイルを置きます。あるいは自分のinit.el に適当に追加します。
agda.el を installGUI 側で使わないと文字化けすることがあるようです。ews.el とか。
C-C control と C を同時に押す M-X esc を押してから X を押すC-X C-F で、Agda1.agda file を開けます。
M-X help-for-help M で、Agda のコマンドの一覧が出ます。C-X o で buffer を切り替えて読みます。
neovim で使う
普通の vim では動かないです。プログラムを書く
Haskell と似たような、それでいて少し違う。そういうもので記述していきます。agda1.agda というファイルを開けます。
module agda1 where
と先頭に書きます。ファイル名と同じでないとだめです。
注意
: の前後は空白を開ける
全角スペースが入らないように気を付ける。
indent で、前の行と繋げる
空白を空けないと一つのワードになる
-> の前後にも空白が必要
-> の優先順位 a -> b -> c は a -> ( b -> c )
() と {} は特別扱いされる
C-C C-L で構文チェック。親切なエラーメッセージに従って直そう。
ex. Parse Error
以下では、この例題を使う。
A → B → A
A → B → A の証明を思い出そう。 A → B → A は、 A → ( B → A ) だった。Goal を一番下に書き、線を引く。
?
----------------
A → B → A
→があるので、→I を使う。つまり、結論部分を上に書く。仮定を使えるようになる。
B → A
---------------- →I
A → B → A
まだ→があるので、繰り返す。
A
---------------- →I2
B → A
---------------- →I1
A → B → A
A → B → A に l1 という名前を付ける。l1 の型が A → B → A である。l1 は証明に相当するλ式を値として持つ。
l1 = λ (x : A ) → λ (y : B ) → xこの時に最後の x は、A という型を持つ項であれば何でも良い。例えば、 ( λ x → x ) x でも良い。つまり、論理式に対する証明は一意ではない。
Agada での A → B → A の証明
まず、A とか B は何かという問題がある。Agda には基本の型として Set がある。A と B が Set であると仮定する。
postulate A B : Set
これは A B という変数が Set という型を持つことを仮定する。
l1 : A → B → A
これは l1 が A → B → A という型を持つことを示す。: は前後に空白がある必要がある。→は -> でも良い。
これだけだと、l1 に値がないと文句を言われる
l1 = ?
とする。ここで emacs で、C-C C-L すると、
?0 : A → B → Aと Agda の buffer に表示される。つまり、この ? の部分に、この型を持つλ項を入れれば証明が終わる。
l1 = λ x → λ y → x
とすれば良い。C-C C-L とすると何も表示されないので、証明が終わったことがわかる。
l1 = λ x → λ y → y
とすると、
B !=< A of type Set
when checking that the expression y has type A
と言われる。y は型Bを持つので、A が要求されているのと合っていない。
暗黙の変数 (implicit variable )
A B を postulate しないで、λ式に直接書くこともできる。この時に、{} で表される暗黙の変数を使ってみよう。
l1' : {A B : Set } → A → B → A
l1' = λ x → λ y → x
{A B : Set } は入力だが、λ式で明示しなくても良い。
l1' = λ { A B } → λ x → λ y → x
と明示しても良い。
Haskell と同じく、引数をパターンとして書くこともできる。
l1 x y = x
暗黙の変数があれば、
l1 {A} {B} x y = x
とすることができる。
矢印の Elimination
次に →を削除する E→ の推論の例を示そう。
l3 : A → ( A → B ) → B
これを証明するためには、入力が二つ必要である。(I→)
l3 x y = ?
結果はBを返す必要があるが、x も y も B ではない。しかし、y は A → B で B を返す関数である。なので、
l3 x y = y ?
とすることができる。y の入力は Aという型である必要がある。x がちょうど型Aなので、それを使う。
l3 x y = y x
λ式で書けば、
l3' = λ x → λ y → y x
A と B がSetだという仮定を暗黙の変数を使って入れてみよう。
Haskell との違い
f0 : {t : Set } -> t -> t
f0 t = ?
f1 : {t t1 : Set } -> t -> (t1 -> t) -> t
f1 = ?
f2 : {t t1 t2 : Set } { a : t2} -> (t2 -> t1) -> (t1 -> t) -> t
f2 = ?
f3 : {t : Set } -> t -> t -> t
f3 = ?
f4 : {t : Set } {a : t } -> ( t -> t ) -> t
f4 = ?
{a : t} などで、足りない入力を補える。
Product
Haksell では直積を表すには data を使ったが、Agda では record を使う。
record _∧_ A B : Set where
constructor _×_
field
proj1 : A
proj2 : B
_ は infix operator の引数を表す。型Aと型Bの直積を作るには、
p1 : A -> B -> A ∧ B
p1 a b = a × b
の様にする。直積の型を表す∧と、直積の値を表す × の二種類が必要になる。
この式は直積の導入(I∧)に相当する。
A B
---------------
A ∧ B
E∧ は、proj1 と proj2 を使う。
open _∧_
p2 : A ∧ B -> A
p2 axb = proj1 axb
axb は、一つの変数である。Agda では空白を入れないと演算子として認識されない。
reocrd を open しないで使いたければ、
p2' : A ∧ B -> A
p2' axb = _∧_.proj1 axb
とする。
porj1 は A ∧ B -> A という型を持つ関数だが、proj1 : A と書かれている。 A ∧ B はrecord 記述中では省略されていると考えても良い。
constructor を使わずに、直接 reocrd を作成することもできる。
p1' : A -> B -> A ∧ B
p1' a b = record { proj1 = a ; proj2 = b }
なので、constructor の定義は必須ではない。
Sum
この論理では普通の論理和は使わず、排他的論理和を使う。A ∨ B はAとBが同時に成り立つことはない。Haskell では data を使って Sum も表していた。Agad では reocrd と区別して data を使う。
data _∨_ A B : Set where
or1 : A → A ∨ B
or2 : B → A ∨ B
構文は record に似ているが、or1 と or2 は constructor である。なので、data を返す必要がある。or1 は A → A ∨ B
という型を持つ関数である。constructor を使って、∨I を使ってみよう。
o1 : B → A ∨ B
o1 x = or2 x
o2 : A → A ∨ B
o2 x = or1 x
これは、B → A ∨ B という論理式の証明になっている。データ型的には、or1 と or2 というtagの付いたunionを作っている。
SumのEliminationは少し複雑で、or1 と or2 の場合分けの証明になる。
o3 : A ∨ A → A
o3 (or1 x) = x
o3 (or2 x) = x
(or1 x) はパターンマッチで、x はλ x のxと同じ scope を持つ局所変数(引数)である。or1 と or2 が同時に起きることはない。
問題4.1
Excercise 4.1 以下の論理式の証明図に対応する lemma を Agda で作れ。
(1) A -> A (2) A -> (B -> A) (3) (A ∧ (A -> B)) -> B (4) B -> (B ∧ (A -> B)) (5) A -> B -> ( A ∨ B ) (6) B -> ( A ∨ (B -> A) ) -> A
等式
agda1.agda 等式の例題 Agda ではどんなことが証明できるのだろうか?
1 + 1 = 2
ぐらいは証明したい。つまり、1 や 2 そして、+ と = を Agda で表すことを行う必要がある。
等式は、
x
----------------
x == x
で導入されるべきだろう。これは、== が constructor だということを示している。record ではなく、data を使う。
data _==_ {A : Set} : A → A → Set where
refl : {x : A} → x == x
== は二引数を持つので、A → A → Set という型を持つ。その constructor は refl ( reflection の略 )で、x を暗黙に受けとって x == x を返す。
e1' : ( x : A ) → x == x
e1' x = refl
問題は elimination だが、
x == y
----------------
y == x
は成立して欲しい。
sym' : x == y → y == x
sym' = ?
ということになる。これは x と y の型がないと怒られるので、
e2 : (x y : A ) → x == y → y == x
e2 = ?
としよう。 入力が三つなので、
sym' : x == y → y == x
sym' x y eq = ?
y == x を作って返せばよいのだが、
sym' x y eq = refl
ではうまくいかない? (どんなエラーが返るか、調べてみよ)
eq は x == y という型なのでパターンで受けることができる。
sym' x y refl = refl
これでもうまくいかない。
x != y of type A
when checking that the pattern refl has type x == y
確かに、x == y である必要がある。
sym' x x refl = refl
とすると、パターンに同じ変数があると怒られる。どうすれば良いのか?
e2 : (x y : A ) → x == y → y == x
e2 x .x refl = refl
と . を使うと入力にある同じ変数を取り扱うことができる。
(x y : A ) を { x y : A }として隠してやると、
e2' : {x y : A } → x == y → y == x
e2' refl = refl
と綺麗に書くことができる。あまり変数を明示しない方が証明が綺麗になることが多い。
問題4.2
Excercise 4.2 以下をAgdaで証明せよ
(1) { x y z : A } → x == y → y == z → x == z
(2) {A B : Set } { x : A } { y : A } { f : A → B } → x == y → f x == f y
(3) {A B : Set } { x : A } { y : A } { f : A → B } → f x == f y → x == y
自然数
自然数は zero から始まって、その次の1、そして、その次の2と、ずーっと続く、つまり、zero と「何かのその次」(successor)の二種類からなる。つまり、data でかける。
data Nat : Set where
zero : Nat
succ : Nat → Nat
ここで、2 は succ ( succ zero ) である。
足し算は、
_+_ : Nat → Nat → Nat
zero + x = ?
(succ x) + y = ?
と場合分けされる。
1 + 1 = 2 は以下のようになる。
nat1 : ( (succ zero ) + (succ zero ) ) == ( succ (succ zero ) )
nat1 = ?
これは入力はなく、等式を返せばよい。
nat1 : ( (succ zero ) + (succ zero ) ) == ( succ (succ zero ) )
nat1 = refl
で証明されたことになる。refl を書いて通れば、等式の証明として十分である。実際には refl は同じ項かどうかを Agda が調べることになる。同じでないものを refl で作ろうとすると Agda が文句をいう。
加算に関するいくつかの法則を証明してみよう。
lemma10 : (x : Nat) → ( zero + x ) == x
lemma10 x = refl
lemma11 : (x : Nat) → ( x + zero ) == x
lemma11 zero = refl
lemma11 (suc x) = cong suc (lemma11 x)
Nat を data の場合分けしている。
lemma11 (suc x) = cong suc (lemma11 x)
は、lemma11 の等式の両辺に S を足したものに相当することを示している。これは、数学的帰納法にもなっていて、zero の場合と、suc で n の場合から n + 1 の場合を証明していることになる。
lemma11 (suc x) = lemma11 (suc x)
は型は合っているが、証明にはならない。
Reasoning
3 + 2 + 1 = 5 + 1 = 6
みたいに等式を推論していく方法が Agda に用意されている。これは、
3 + 2 + 1 = 5 + 1
5 + 1 = 6
から、trans を使って、
3 + 2 + 1 = 6
を導けば良い。
open import Relation.Binary.PropositionalEquality
open ≡-Reasoning
ここでは == の代わりに既に ≡ が定義されているので、それを使う。
add-sym : (x y : Nat) -> x + y ≡ y + x add-sym zero zero = refl add-sym zero (suc y) = cong suc (add-sym zero y) add-sym (suc x) zero = cong suc (add-sym x zero) add-sym (suc x) (suc y) = ?ここで、
add-sym (suc x) (suc y) = begin
(suc x) + (suc y)
≡⟨ ? ⟩
(suc y) + (suc x)
∎
とできる。? に
a ≡ b
の形をした変形規則を入れていく。
≡⟨ refl ⟩は、同じ normal form を持っていることを示している。変形を丁寧にやりたい時に使う。
≡⟨⟩とも書ける。
≡⟨ cong suc (add-sym (suc x) y) ⟩で証明できるはずだが。
問題4.4
加算の反射率の証明を完成せよ。
add-sym : (x y : Nat) → ( x + y ) == ( y + x )
add-sym zero y = trans {!!} {!!}
add-sym (suc x) y = {!!}
かけ算と足し算
かけ算と足し算の交換則を Agda 上に作成してみよう。