GCamlで型レベル論理プログラミングっぽいこと

GCamlというOCamlオーバーロード機能を追加した言語あるが、それの型推論機構をうまく使って、論理プログラミングっぽいことができないかと試行錯誤してみた。

まずは基本的な定数項を型として定義する。

type hanako = Hanako;;   
type taro   = Taro;;
type ichiro = Ichiro;;
type michio = Michio;;
type emiko  = Emiko;;
type soichi = Soichi;;
type haruo  = Haruo;;

次に、親子関係に関する事実parentをgenericな値として定義する。

let parent = generic (Hanako, Ichiro)   (* 花子の親は一郎 *)
                   | (Taro, Ichiro)     (* 太郎の親は一郎 *)
                   | (Ichiro, Michio)   (* 一郎の親は道夫 *)
                   | (Emiko, Michio)    (* 笑子の親は道夫 *)
                   | (Michio, Haruo)    (* 道夫の親は春雄 *)
                   | (Soichi, Haruo)    (* 宗一の親は春雄 *)
;;

parentに関する問い合わせは、以下のように関数を定義して使う。

let who_is_parent (x:'a) (y:'b) = ignore(parent:('a * 'b));;

実行例:

# who_is_parent Hanako;;
- : ichiro -> unit = <fun>      --- 花子の親は一郎です
# who_is_parent Ichiro;;
- : michio -> unit = <fun>      --- 一郎の親は道夫です

論理プログラミングっぽく逆方向の問い合わせもできる。。。

# (fun x y -> who_is_parent y x) Haruo;;
- : michio -> unit = <fun>

推論に失敗すると、型エラーとなる。

# who_is_parent Haruo;;
Type haruo -> 'a -> unit is not an instance of overloaded type
{ 'a * 'b < [| hanako * ichiro | taro * ichiro | ichiro * michio
             | emiko * michio | michio * haruo | soichi * haruo |] } =>
  'a -> 'b -> unit.

次に、祖父母関係を表す規則grand_parentをparentを使って定義してみる。

let grand_parent (x:'t) = fst (parent:('a * 't)), snd (parent:('t * 'b));;

ここで、余計に思える引数(x:'t)は実は重要で、これがないと、'tが単相型と判断されてしまうので、grand_parentが一回ぽっきりしか使えなくなってしまう。

そして、grand_parentに関する問い合わせは以下のように関数定義する。

let who_is_grandparent (x:'a) (y:'b) t = ignore((grand_parent t):('a * 'b));;

実行例はつぎのとおり。

# who_is_grandparent Taro;;
- : michio -> ichiro -> unit = <fun>   --- 太郎の祖父母は道夫
# who_is_grandparent Ichiro;;
- : haruo -> michio -> unit = <fun>    --- 一郎の祖父母は春雄
# (fun x y -> who_is_grandparent y x) Michio;;
- : hanako -> ichiro -> unit = <fun>   --- 道夫は花子の祖父母
# who_is_grandparent Michio;;  --- 失敗例
Type michio -> 'a -> 'b -> unit is not an instance of overloaded type
{ 'd -> 'e * 'f < { 'a * 'b < [| hanako * ichiro | taro * ichiro
                               | ichiro * michio | emiko * michio
                               | michio * haruo | soichi * haruo |],
                    'c * 'a < [| hanako * ichiro | taro * ichiro
                               | ichiro * michio | emiko * michio
                               | michio * haruo | soichi * haruo |] } =>
                    'a -> 'c * 'b } => 'e -> 'f -> 'd -> unit.
# (fun x y -> who_is_grandparent y x) Haruo;;
Type 'a -> haruo -> 'b -> unit is not an instance of overloaded type
{ 'd -> 'e * 'f < { 'a * 'b < [| hanako * ichiro | taro * ichiro
                               | ichiro * michio | emiko * michio
                               | michio * haruo | soichi * haruo |],
                    'c * 'a < [| hanako * ichiro | taro * ichiro
                               | ichiro * michio | emiko * michio
                               | michio * haruo | soichi * haruo |] } =>
                    'a -> 'c * 'b } => 'e -> 'f -> 'd -> unit.

最後の例(春雄は「誰」の祖父母?)が失敗しているのは、型推論のユニフィケーションにはバックトラックが無いからかと思われる。

Haskellの型クラスを使っても同じようなことができるかもしれない。