F# で型レベルプログラミング
今 F# で依存型をシミュレートしようと頑張ってる のだけど、その途中でちょっと面白いことができたのでそこだけ切り出してまとめておく。
F# で型レベルプログラミングができたのだ。
(依存型シミュレートはそれなりに納得できる形になったら記事にするよ!)
いつものように inline functions と SRTP を悪用していくわけだけど、F# の型引数にはある望まざる性質があるので、型を値として受け渡しするラッパを用意しておく。
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
こいつは空っぽの struct なので、値レベルでは何もしない。ただ型を受け渡すだけの存在だ。
で、 F# で型の評価を進める、つまりある型から別の型を作る方法はあまり多くなくて、そのうち SRTP で制約として書けるものは「メンバを生やす」方法のみだ。
そこで、型 A
に Ty<A> -> Ty<B>
なるメンバ eval
を生やすことにする。自明な例から行ってみよう。
1: 2: 3: 4: 5: |
|
また、 F# は型の評価を暗黙的に進めてくれないので、項の評価ですよと言いくるめて押し通す。
1: 2: |
|
inline functions はコンパイル時に展開されるし、 eval
は型の変形しかしないので、実行時には何の処理も行われない。
で、True
も False
も正規形なので、 eval
しようがそのままだ。
1: 2: 3: 4: 5: |
|
マウスを変数名に重ねたりタッチしたりすると変数の型が見られるはずなので、eval の結果はいちいち変数に束縛しておく。一応 stringify した結果も載せておく。
また、church encoding を知ってると話が早いのだけど、真偽値自体に car/cdr の機能を持たせておくととても便利である。どう便利かはすぐにわかる。
1: 2: 3: 4: 5: |
|
True
が car、False
が cdr の機能を持つ。
これを使うと、Not
が書ける。
1: 2: 3: 4: |
|
ここが今回の記事の要だ!! Not< ^A >
の eval
において、
(1): まず Ty< ^A >
を eval
してその結果 Ty< ^Bool >
を取り出す。
(2): ^Bool
がメンバ ifThenElse
を持っているものとして、それを (ty<False>, ty<True>)
に適用する。
ここで、もし ^Bool
が True
だったら ifThenElse
は car であり、ty<False>
が取り出される。False
だったらその逆になって、どちらでもなかったらコンパイルエラーになる。
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
つまり eval
結果の型のメンバ ifThenElse
の有無によって型の型(カインド)を判別している。(Haskell では同じことを -XDataKinds でやる)
これが理解できれば、And
も Or
も同じことをするだけだ。
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: |
|
また、 if-then-else を型レベルに持ち上げることすらできる。
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: |
|
いよいよプログラミングらしくなってきた。
自然数も同様で、こちらも church encoding 的に、型自体に加算の機能をもたせる。
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: |
|
減算はちょっと面倒だが、 S< ^X > - S< ^Y >
と ^X - Z
だけを定義しておけば結果が負になるような減算は型エラー(メンバが見つからない)にすることができる。
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: |
|
大小比較も同様。
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: |
|
等値判定は正規形の型に判定関数を定義して、 eval したあとそれを呼び出せばよいが、煩雑な割に別に結果は面白くないので省略する。
さて、 F# では制約解決器で無限ループを起こさせることができて(制約で自分自身を要求することで起こせる)、
1:
|
|
というエラーメッセージが出る。この事実は、 F# の型レベル言語が turing complete であることを示唆している。例えば型レベルで de Bruijn indexed な型無しラムダ計算をできるのではないだろうか?そのうち、やってみるかも……(もしやってみてできたら教えてね)
type StructAttribute =
inherit Attribute
new : unit -> StructAttribute
Full name: Microsoft.FSharp.Core.StructAttribute
--------------------
new : unit -> StructAttribute
struct
override ToString : unit -> string
end
Full name: 03-08-fsharp-type-level-computation_.Ty<_>
Full name: 03-08-fsharp-type-level-computation_.Ty`1.ToString
Full name: Microsoft.FSharp.Core.Operators.typeof
Full name: 03-08-fsharp-type-level-computation_.ty
union case True.True: True
--------------------
type True =
| True
static member eval : Ty<True> -> Ty<True>
static member ifThenElse : Ty<True> * x:'a * y:'b -> 'a
Full name: 03-08-fsharp-type-level-computation_.True
Full name: 03-08-fsharp-type-level-computation_.True.eval
union case False.False: False
--------------------
type False =
| False
static member eval : Ty<False> -> Ty<False>
static member ifThenElse : Ty<False> * x:'a * y:'b -> 'b
Full name: 03-08-fsharp-type-level-computation_.False
Full name: 03-08-fsharp-type-level-computation_.False.eval
Full name: 03-08-fsharp-type-level-computation_.eval
Full name: 03-08-fsharp-type-level-computation_.true_
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: 03-08-fsharp-type-level-computation_.false_
Full name: 03-08-fsharp-type-level-computation_.True.ifThenElse
Full name: 03-08-fsharp-type-level-computation_.False.ifThenElse
union case Not.Not: 'a -> Not<'a>
--------------------
type Not<'a> =
| Not of 'a
static member eval : Ty<Not<'A>> -> Ty<'B> (requires member eval and member ifThenElse)
Full name: 03-08-fsharp-type-level-computation_.Not<_>
Full name: 03-08-fsharp-type-level-computation_.Not`1.eval
Full name: 03-08-fsharp-type-level-computation_.notTrue
Full name: 03-08-fsharp-type-level-computation_.notNotTrue
union case BadType.BadType: BadType
--------------------
type BadType =
| BadType
static member eval : Ty<BadType> -> Ty<BadType>
Full name: 03-08-fsharp-type-level-computation_.BadType
Full name: 03-08-fsharp-type-level-computation_.BadType.eval
union case And.And: 'a * 'b -> And<'a,'b>
--------------------
type And<'a,'b> =
| And of 'a * 'b
static member eval : Ty<And<'A,'B>> -> Ty<'C> (requires member eval and member ifThenElse and member eval)
Full name: 03-08-fsharp-type-level-computation_.And<_,_>
Full name: 03-08-fsharp-type-level-computation_.And`2.eval
union case Or.Or: 'a * 'b -> Or<'a,'b>
--------------------
type Or<'a,'b> =
| Or of 'a * 'b
static member eval : Ty<Or<'A,'B>> -> Ty<'C> (requires member eval and member ifThenElse and member eval)
Full name: 03-08-fsharp-type-level-computation_.Or<_,_>
Full name: 03-08-fsharp-type-level-computation_.Or`2.eval
Full name: 03-08-fsharp-type-level-computation_.x
union case IfThenElse.IfThenElse: '_bool * 'a * 'b -> IfThenElse<'_bool,'a,'b>
--------------------
type IfThenElse<'_bool,'a,'b> =
| IfThenElse of '_bool * 'a * 'b
static member eval : Ty<IfThenElse<'X,'A,'B>> -> Ty<'EvalAorB> (requires member eval and member ifThenElse and member eval and member eval)
Full name: 03-08-fsharp-type-level-computation_.IfThenElse<_,_,_>
Full name: 03-08-fsharp-type-level-computation_.IfThenElse`3.eval
| A
static member eval : 'a -> Ty<A>
Full name: 03-08-fsharp-type-level-computation_.A
Full name: 03-08-fsharp-type-level-computation_.A.eval
| B
static member eval : 'a -> Ty<B>
Full name: 03-08-fsharp-type-level-computation_.B
Full name: 03-08-fsharp-type-level-computation_.B.eval
Full name: 03-08-fsharp-type-level-computation_.Code
Full name: 03-08-fsharp-type-level-computation_.result
| Z
static member add : Ty<Z> * y:Ty<'a> -> Ty<'b> (requires member eval)
static member eval : x:Ty<Z> -> Ty<Z>
static member gt : Ty<S<'a>> * Ty<Z> -> Ty<True>
static member gt : Ty<Z> * 'a -> Ty<False>
static member sub : x:Ty<'a> * Ty<Z> -> Ty<'b> (requires member eval)
Full name: 03-08-fsharp-type-level-computation_.Z
Full name: 03-08-fsharp-type-level-computation_.Z.eval
Full name: 03-08-fsharp-type-level-computation_.Z.add
| S of 'n
static member add : Ty<S<'X>> * Ty<'Y> -> Ty<S<'Z>> (requires member eval and member add)
static member eval : Ty<S<'N>> -> Ty<S<'N'>> (requires member eval)
static member gt : Ty<S<'X>> * Ty<S<'Y>> -> Ty<'Z> (requires member eval and member gt and member eval)
static member sub : Ty<S<'X>> * Ty<S<'Y>> -> Ty<'Z> (requires member eval and member eval and member sub)
Full name: 03-08-fsharp-type-level-computation_.S<_>
Full name: 03-08-fsharp-type-level-computation_.S`1.eval
Full name: 03-08-fsharp-type-level-computation_.S`1.add
union case Add.Add: 'x * 'y -> Add<'x,'y>
--------------------
type Add<'x,'y> =
| Add of 'x * 'y
static member eval : Ty<Add<'X,'Y>> -> 'a (requires member eval and member add)
Full name: 03-08-fsharp-type-level-computation_.Add<_,_>
Full name: 03-08-fsharp-type-level-computation_.Add`2.eval
Full name: 03-08-fsharp-type-level-computation_.One
Full name: 03-08-fsharp-type-level-computation_.Three
Full name: 03-08-fsharp-type-level-computation_.Five
Full name: 03-08-fsharp-type-level-computation_.five
Full name: 03-08-fsharp-type-level-computation_.Z.sub
Full name: 03-08-fsharp-type-level-computation_.S`1.sub
union case Sub.Sub: 'x * 'y -> Sub<'x,'y>
--------------------
type Sub<'x,'y> =
| Sub of 'x * 'y
static member eval : Ty<Sub<'X,'Y>> -> 'a (requires member eval and member eval and member sub)
Full name: 03-08-fsharp-type-level-computation_.Sub<_,_>
Full name: 03-08-fsharp-type-level-computation_.Sub`2.eval
Full name: 03-08-fsharp-type-level-computation_.Two
Full name: 03-08-fsharp-type-level-computation_.Six
Full name: 03-08-fsharp-type-level-computation_.six
Full name: 03-08-fsharp-type-level-computation_.Z.gt
Full name: 03-08-fsharp-type-level-computation_.Z.gt
Full name: 03-08-fsharp-type-level-computation_.S`1.gt
union case GT.GT: 'x * 'y -> GT<'x,'y>
--------------------
type GT<'x,'y> =
| GT of 'x * 'y
static member eval : Ty<GT<'X,'Y>> -> 'a (requires member eval and member gt and member eval)
Full name: 03-08-fsharp-type-level-computation_.GT<_,_>
Full name: 03-08-fsharp-type-level-computation_.GT`2.eval
| GT of 'x * 'y
static member eval : Ty<LT<'X,'Y>> -> 'a (requires member eval and member gt and member eval)
Full name: 03-08-fsharp-type-level-computation_.LT<_,_>
union case LT.GT: 'x * 'y -> LT<'x,'y>
--------------------
type GT<'x,'y> =
| GT of 'x * 'y
static member eval : Ty<GT<'X,'Y>> -> 'a (requires member eval and member gt and member eval)
Full name: 03-08-fsharp-type-level-computation_.GT<_,_>
Full name: 03-08-fsharp-type-level-computation_.LT`2.eval
Full name: 03-08-fsharp-type-level-computation_.y