F# で型レベルプログラミング

F# で依存型をシミュレートしようと頑張ってる のだけど、その途中でちょっと面白いことができたのでそこだけ切り出してまとめておく。

F# で型レベルプログラミングができたのだ。

(依存型シミュレートはそれなりに納得できる形になったら記事にするよ!)

いつものように inline functions と SRTP を悪用していくわけだけど、F# の型引数にはある望まざる性質があるので、型を値として受け渡しするラッパを用意しておく。

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
[<Struct>]
type Ty<'Type> =
  override __.ToString() =  // 型の表示用
    typeof<'Type>.ToString()
                 .Replace("FSI_0001+", "")
                 .Replace("[", "<")
                 .Replace("]", ">")
                 .Replace("`1", "")
                 .Replace("`2", "")

let inline ty< ^Type > : Ty< ^Type > = Ty()

こいつは空っぽの struct なので、値レベルでは何もしない。ただ型を受け渡すだけの存在だ。

で、 F# で型の評価を進める、つまりある型から別の型を作る方法はあまり多くなくて、そのうち SRTP で制約として書けるものは「メンバを生やす」方法のみだ。

そこで、型 ATy<A> -> Ty<B> なるメンバ eval を生やすことにする。自明な例から行ってみよう。

1: 
2: 
3: 
4: 
5: 
type True = True with
  static member inline eval (_: Ty<True>) = ty<True>

type False = False with
  static member inline eval (_: Ty<False>) = ty<False>

また、 F# は型の評価を暗黙的に進めてくれないので、項の評価ですよと言いくるめて押し通す。

1: 
2: 
let inline eval (x: Ty< ^A >) : Ty< ^B > =
  (^A: (static member inline eval: Ty< ^A > -> Ty< ^B >) x)

inline functions はコンパイル時に展開されるし、 eval は型の変形しかしないので、実行時には何の処理も行われない。

で、TrueFalse も正規形なので、 eval しようがそのままだ。

1: 
2: 
3: 
4: 
5: 
let true_ = eval ty<True>
true_ |> printfn "%A" // True

let false_ = eval ty<False>
false_ |> printfn "%A" // False

マウスを変数名に重ねたりタッチしたりすると変数の型が見られるはずなので、eval の結果はいちいち変数に束縛しておく。一応 stringify した結果も載せておく。

また、church encoding を知ってると話が早いのだけど、真偽値自体に car/cdr の機能を持たせておくととても便利である。どう便利かはすぐにわかる。

1: 
2: 
3: 
4: 
5: 
type True with
  static member inline ifThenElse (_: Ty<True>, x, y) = x 

type False with
  static member inline ifThenElse (_: Ty<False>, x, y) = y

True が car、False が cdr の機能を持つ。

これを使うと、Not が書ける。

1: 
2: 
3: 
4: 
type Not<'a> = Not of 'a with
  static member inline eval (_: Ty<Not< ^A >>) : Ty< ^B >
    when ^A: (static member eval: Ty< ^A > -> Ty< ^Bool >) = // (1)
    (^Bool: (static member ifThenElse: Ty< ^Bool > * _ * _ -> Ty< ^B >) ty< ^Bool >, ty<False>, ty<True>) // (2)

ここが今回の記事の要だ!! Not< ^A >eval において、

(1): まず Ty< ^A >eval してその結果 Ty< ^Bool > を取り出す。

(2): ^Bool がメンバ ifThenElse を持っているものとして、それを (ty<False>, ty<True>) に適用する。

ここで、もし ^BoolTrue だったら ifThenElse は car であり、ty<False> が取り出される。False だったらその逆になって、どちらでもなかったらコンパイルエラーになる。

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let notTrue = eval ty<Not<True>>
notTrue |> printfn "%A" // False

let notNotTrue = eval ty<Not<Not<True>>> // 先に中身を eval するので入れ子も OK
notNotTrue |> printfn "%A" // True

// let error1 = eval ty<Not<bool>>
// ^ error FS0001: The type 'bool' does not support the operator 'eval'

type BadType = BadType with
  static member eval (_: Ty<BadType>) = ty<BadType>

// let error2 = eval ty<Not<BadType>>
// ^ error FS0001: The type 'BadType' does not support the operator 'ifThenElse'

つまり eval 結果の型のメンバ ifThenElse の有無によって型の型(カインド)を判別している。(Haskell では同じことを -XDataKinds でやる)

これが理解できれば、AndOr も同じことをするだけだ。

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
type And<'a, 'b> = And of 'a * 'b with
  static member inline eval (_: Ty<And< ^A, ^B >>) : Ty< ^C >
    when ^A: (static member eval: Ty< ^A > -> Ty< ^A' >)
     and ^B: (static member eval: Ty< ^B > -> Ty< ^B' >) =
    (^A': (static member ifThenElse: _*_*_ -> Ty< ^C >) ty< ^A' >,ty< ^B' >,ty<False>)

type Or<'a, 'b>  = Or of 'a * 'b with
  static member inline eval (_: Ty<Or< ^A, ^B >>) : Ty< ^C >
    when ^A: (static member eval: Ty< ^A > -> Ty< ^A' >)
     and ^B: (static member eval: Ty< ^B > -> Ty< ^B' >) =
    (^A': (static member ifThenElse: _*_*_ -> Ty< ^C >) ty< ^A' >,ty<True>,ty< ^B' >)

let x = eval ty<And<Not<False>, Or<Not<True>, True>>>
// not false && (not true || true)
x |> printfn "%A" // True

また、 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: 
type IfThenElse<'_bool, 'a, 'b> = IfThenElse of '_bool * 'a * 'b with
  static member inline eval (_: Ty<IfThenElse< ^X, ^A, ^B>>) : Ty< ^EvalAorB >
    when ^X: (static member eval: Ty< ^X > -> Ty< ^Bool >) =
    (^Bool: (static member ifThenElse: _*_*_ -> Ty< ^EvalAorB >) ty< ^Bool >,eval ty< ^A >,eval ty< ^B >)
    // 制約に絡まないなら eval は値レベルでやってもよい

// 適当な型定数
type A = A with static member eval _ = ty<A>
type B = B with static member eval _ = ty<B> 

type Code = 
  IfThenElse<
    Or<
      Not<True>,
      IfThenElse<
        Or<
          Not<True>, False>,
        BadType, Not<False>>>, // それぞれの型の一致は見ない。どうせ静的に決まる
    A, B>

let result = eval ty<Code>
result |> printfn "%A" // A

いよいよプログラミングらしくなってきた。

自然数も同様で、こちらも 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: 
type Z = Z with
  static member eval (x: Ty<Z>) = x
  static member inline add (_: Ty<Z>, y) = eval y

type S<'n> = S of 'n with
  static member inline eval (_: Ty<S< ^N >>) : _
    when ^N: (static member eval: Ty< ^N > -> Ty< ^N' >) = ty<S< ^N' >>
  static member inline add (_: Ty<S< ^X >>, _: Ty< ^Y >) : _
    when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
     and ^X': (static member add: Ty< ^X' > * Ty< ^Y > -> Ty< ^Z >) =
    ty<S< ^Z >>

type Add<'x, 'y> = Add of 'x * 'y with
  static member inline eval (_: Ty<Add< ^X, ^Y>>) : _
    when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >) =
    (^X': (static member add: _*_ -> _) ty< ^X' >,ty< ^Y >)

type One = S<Z>
type Three = Add<One, Add<One, One>>
type Five = S<Add<Three, One>>

let five = eval ty<Five>
five |> printfn "%A" // S<S<S<S<S<Z>>>>>

減算はちょっと面倒だが、 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: 
type Z with
  static member inline sub (x, _: Ty<Z>) = eval x

type S<'n> with
  static member inline sub (_: Ty<S< ^X> >, _: Ty<S< ^Y >>) : _
    when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
     and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >)
     and ^Y': (static member sub: Ty< ^X' > * Ty< ^Y' > -> Ty< ^Z >) =
    ty< ^Z >

type Sub<'x, 'y> = Sub of 'x * 'y with
  static member inline eval (_: Ty<Sub< ^X, ^Y>>) : _
    when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
     and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >) =
    (^Y': (static member sub: _*_ -> _) ty< ^X' >,ty< ^Y' >)

type Two = Sub<Three, One>
type Six = Sub<Add<Five, Two>, One>

let six = eval ty<Six>
six |> printfn "%A" // S<S<S<S<S<S<Z>>>>>>

// let error = eval ty<Sub<Two, Six>>
// ^ Type constraint mismatch. The type 'Ty<Z>' is not compatible with type 'Ty<S<'a>>'

大小比較も同様。

 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: 
type Z with
  static member inline gt (_: Ty<S<_>>, _: Ty<Z>) = ty<True>
  static member inline gt (_: Ty<Z>, _) = ty<False>

type S<'n> with
  static member inline gt (_: Ty<S< ^X >>, _: Ty<S< ^Y >>) : _
    when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
     and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >)
     and (^X' or ^Y'): (static member gt: Ty< ^X' > * Ty< ^Y' > -> Ty< ^Z >) =
    ty< ^Z >

type GT<'x, 'y> = GT of 'x * 'y with
  static member inline eval (_: Ty<GT< ^X, ^Y>>) : _
    when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
     and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >) =
    ((^X' or ^Y'): (static member gt: _*_ -> _) ty< ^X' >,ty< ^Y' >)

type LT<'x, 'y> = GT of 'x * 'y with
  static member inline eval (_: Ty<LT< ^X, ^Y>>) : _
    when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
     and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >) =
    ((^X' or ^Y'): (static member gt: _*_ -> _) ty< ^Y' >,ty< ^X' >)

let y = eval ty<And<GT<Six, Three>, LT<Sub<Five, One>, Add<One, Three>>>>

y |> printfn "%A" // False

等値判定は正規形の型に判定関数を定義して、 eval したあとそれを呼び出せばよいが、煩雑な割に別に結果は面白くないので省略する。

さて、 F# では制約解決器で無限ループを起こさせることができて(制約で自分自身を要求することで起こせる)、

1: 
error FS0465: Type inference problem too complicated (maximum iteration depth reached). Consider adding further type annotations.

というエラーメッセージが出る。この事実は、 F# の型レベル言語が turing complete であることを示唆している。例えば型レベルで de Bruijn indexed な型無しラムダ計算をできるのではないだろうか?そのうち、やってみるかも……(もしやってみてできたら教えてね)

Multiple items
type StructAttribute =
  inherit Attribute
  new : unit -> StructAttribute

Full name: Microsoft.FSharp.Core.StructAttribute

--------------------
new : unit -> StructAttribute
type Ty<'Type> =
  struct
    override ToString : unit -> string
  end

Full name: 03-08-fsharp-type-level-computation_.Ty<_>
override Ty.ToString : unit -> string

Full name: 03-08-fsharp-type-level-computation_.Ty`1.ToString
val typeof<'T> : System.Type

Full name: Microsoft.FSharp.Core.Operators.typeof
val ty<'Type> : Ty<'Type>

Full name: 03-08-fsharp-type-level-computation_.ty
Multiple items
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
static member True.eval : Ty<True> -> Ty<True>

Full name: 03-08-fsharp-type-level-computation_.True.eval
Multiple items
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
static member False.eval : Ty<False> -> Ty<False>

Full name: 03-08-fsharp-type-level-computation_.False.eval
val eval : x:Ty<'A> -> Ty<'B> (requires member eval)

Full name: 03-08-fsharp-type-level-computation_.eval
val x : Ty<'A> (requires member eval)
val true_ : Ty<True>

Full name: 03-08-fsharp-type-level-computation_.true_
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val false_ : Ty<False>

Full name: 03-08-fsharp-type-level-computation_.false_
static member True.ifThenElse : Ty<True> * x:'a * y:'b -> 'a

Full name: 03-08-fsharp-type-level-computation_.True.ifThenElse
val x : 'a
val y : 'b
static member False.ifThenElse : Ty<False> * x:'a * y:'b -> 'b

Full name: 03-08-fsharp-type-level-computation_.False.ifThenElse
Multiple items
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<_>
static member Not.eval : Ty<Not<'A>> -> Ty<'B> (requires member eval and member ifThenElse)

Full name: 03-08-fsharp-type-level-computation_.Not`1.eval
val notTrue : Ty<False>

Full name: 03-08-fsharp-type-level-computation_.notTrue
val notNotTrue : Ty<True>

Full name: 03-08-fsharp-type-level-computation_.notNotTrue
Multiple items
union case BadType.BadType: BadType

--------------------
type BadType =
  | BadType
  static member eval : Ty<BadType> -> Ty<BadType>

Full name: 03-08-fsharp-type-level-computation_.BadType
static member BadType.eval : Ty<BadType> -> Ty<BadType>

Full name: 03-08-fsharp-type-level-computation_.BadType.eval
Multiple items
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<_,_>
static member And.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`2.eval
Multiple items
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<_,_>
static member Or.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`2.eval
val x : Ty<True>

Full name: 03-08-fsharp-type-level-computation_.x
Multiple items
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<_,_,_>
static member IfThenElse.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`3.eval
type A =
  | A
  static member eval : 'a -> Ty<A>

Full name: 03-08-fsharp-type-level-computation_.A
union case A.A: A
static member A.eval : 'a -> Ty<A>

Full name: 03-08-fsharp-type-level-computation_.A.eval
type B =
  | B
  static member eval : 'a -> Ty<B>

Full name: 03-08-fsharp-type-level-computation_.B
union case B.B: B
static member B.eval : 'a -> Ty<B>

Full name: 03-08-fsharp-type-level-computation_.B.eval
type Code = IfThenElse<Or<Not<True>,IfThenElse<Or<Not<True>,False>,BadType,Not<False>>>,A,B>

Full name: 03-08-fsharp-type-level-computation_.Code
val result : Ty<A>

Full name: 03-08-fsharp-type-level-computation_.result
type Z =
  | 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
union case Z.Z: Z
static member Z.eval : x:Ty<Z> -> Ty<Z>

Full name: 03-08-fsharp-type-level-computation_.Z.eval
val x : Ty<Z>
static member Z.add : Ty<Z> * y:Ty<'a> -> Ty<'b> (requires member eval)

Full name: 03-08-fsharp-type-level-computation_.Z.add
val y : Ty<'a> (requires member eval)
type S<'n> =
  | 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<_>
union case S.S: 'n -> S<'n>
static member S.eval : Ty<S<'N>> -> Ty<S<'N'>> (requires member eval)

Full name: 03-08-fsharp-type-level-computation_.S`1.eval
static member S.add : Ty<S<'X>> * Ty<'Y> -> Ty<S<'Z>> (requires member eval and member add)

Full name: 03-08-fsharp-type-level-computation_.S`1.add
Multiple items
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<_,_>
static member Add.eval : Ty<Add<'X,'Y>> -> 'a (requires member eval and member add)

Full name: 03-08-fsharp-type-level-computation_.Add`2.eval
type One = S<Z>

Full name: 03-08-fsharp-type-level-computation_.One
type Three = Add<One,Add<One,One>>

Full name: 03-08-fsharp-type-level-computation_.Three
type Five = S<Add<Three,One>>

Full name: 03-08-fsharp-type-level-computation_.Five
val five : Ty<S<S<S<S<S<Z>>>>>>

Full name: 03-08-fsharp-type-level-computation_.five
static member Z.sub : x:Ty<'a> * Ty<Z> -> Ty<'b> (requires member eval)

Full name: 03-08-fsharp-type-level-computation_.Z.sub
val x : Ty<'a> (requires member eval)
static member S.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`1.sub
Multiple items
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<_,_>
static member Sub.eval : Ty<Sub<'X,'Y>> -> 'a (requires member eval and member eval and member sub)

Full name: 03-08-fsharp-type-level-computation_.Sub`2.eval
type Two = Sub<Three,One>

Full name: 03-08-fsharp-type-level-computation_.Two
type Six = Sub<Add<Five,Two>,One>

Full name: 03-08-fsharp-type-level-computation_.Six
val six : Ty<S<S<S<S<S<S<Z>>>>>>>

Full name: 03-08-fsharp-type-level-computation_.six
static member Z.gt : Ty<S<'a>> * Ty<Z> -> Ty<True>

Full name: 03-08-fsharp-type-level-computation_.Z.gt
static member Z.gt : Ty<Z> * 'a -> Ty<False>

Full name: 03-08-fsharp-type-level-computation_.Z.gt
static member S.gt : Ty<S<'X>> * Ty<S<'Y>> -> Ty<'Z> (requires member eval and member gt and member eval)

Full name: 03-08-fsharp-type-level-computation_.S`1.gt
Multiple items
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<_,_>
static member GT.eval : Ty<GT<'X,'Y>> -> 'a (requires member eval and member gt and member eval)

Full name: 03-08-fsharp-type-level-computation_.GT`2.eval
type LT<'x,'y> =
  | 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<_,_>
Multiple items
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<_,_>
static member LT.eval : Ty<LT<'X,'Y>> -> 'a (requires member eval and member gt and member eval)

Full name: 03-08-fsharp-type-level-computation_.LT`2.eval
val y : Ty<False>

Full name: 03-08-fsharp-type-level-computation_.y