こわくない Idris (6) 補足
こわくない Idris (6) - M59のブログ
1年前に書いた記事を読み返していたら、証明がすぐには理解できなかった。証明の内容を言葉で分かるように書いておきたいと思う。
次のコードのMyHead.head
は、全域函数だ。
%default total namespace MyHead head : (l : List a) -> (isCons l = True) -> a head Nil prf = FalseElim (replace {P=P} prf ()) where P : Bool -> Type P False = () P True = _|_ head (x :: xs) _ = x
head函数は、最初の引数のリストの先頭の要素を返す函数だ。head函数の2番目の引数に、リストが空でないことの証明を取る。リストが空でない証明を要求することで、空のリストから先頭の値を取り出す操作を防いでいる。
最後の行のhead (x :: xs) _ = x
は、空でないリストの最初の要素を取り出すだけのコードで、何の問題もない。証明は単純に無視している。
問題は、head Nil prf = ...
となっている部分が何をしているかだ。この部分は、最初の引数にNilが渡されてきたにも関わらず、そのリストが空でないという証明も渡されてきた場合についての記述だ。「そんなの、あり得ないじゃないか。」そう、あり得ない。矛盾だ。
headが全域函数であることを示すには、すべての場合でa型の値を返すということを示せば良いのだけれども、引数に矛盾がある時にはどうすればいいのか。実は、矛盾がある場合には、どのような型の値も作ることができる。その事実を表すのが、FalseElim
だ。
FalseElim : _|_ -> a
FalseElim
のFalse
が指しているのは、Bool
型のFalse
ではなく_|_
のこと。_|_
は矛盾だとか偽だとか、そういう意味だと思える。もし矛盾がある(偽が恒真だと示せる)ならば、そこから任意の命題が導ける。この規則があれば、矛盾から任意の型の値を「作る」ことができる。そんな場合はあり得ないのだけれども、あり得ないがゆえに。
最初の引数がNil
のとき、次の引数の型はFalse = True
になる。ここから、矛盾を導こう。
replace
函数は、次の型を持つ。
replace : {a:_} -> {x:_} -> {y:_} -> {P : a -> Type} -> x = y -> P x -> P y
直感的には、x = y
が成り立つ場合には、命題P x
とP y
は同値だから、P x
をP y
に置き換えても問題はないというふうに読める。ここでP
を
P : Bool -> Type P False = () P True = _|_
とすれば、replace {x=False} {y=True} {P=P}
の型が
False = True -> P False -> P True
すなわち
False = True -> () -> _|_
となることが分かるだろう。そこにprf : False = True
と() : ()
を渡せば、望んだ型の値replace {P=P} prf () : _|_
が得られる。
分かりやすくなるように書こうとしたけど、あんまり分かりやすくならなかった。
現在のIdris
現在のバージョン Idris 0.9.14 では、head
は次のように定義されている。
||| Get the first element of a non-empty list ||| @ ok proof that the list is non-empty head : (l : List a) -> {auto ok : isCons l = True} -> a head [] {ok=Refl} impossible head (x::xs) {ok=p} = x
impossibleを使えば、引数が[]で、暗黙の引数ok
がRefl
であるケースが不可能であるという指定になる。Refl : x = x
とisCons [] = True
で型がユニフィケーションできないので、確かにこのケースが不可能であると型推論器は判断できるし、(=)
はコンストラクタがRefl
だけなので、これで全部のケースを尽くしているので、結局totalだということになる。つまり、1年前に書いたややこしいコードは本当は必要なかった。