type families と fundeps の、単一化に関する微妙な差異
昨日の記事に関連するのだけど、 fundeps と type families で一見 同じように見えるが 微妙に異なる挙動を見つけた。ずいぶん込み入った例だけれど、さしあたりの理解のためにこの記事を書く。
ストーリー
例のごとく、私は full-sessionsにおいて 繰り返しを含むプロトコルを表現した型を表現するため、 Haskellで「見かけ上、構造的な再帰型」が欲しいのである(こういう表現が正しいかどうかわからないけれど…)。
例えば
data F u = F u data G u = G u
という型があるとき、
x = F (G x)
と定義された x の型をうまくHaskellの型検査器に推論させたい。 もちろんHaskellでそんなことはできない *1が、例えばこれを
x = rec (F (G x))
のように、ちょっとした仕掛けを入れてやることで、
x :: Rec (F (G Var))
のように、「いかにも再帰型っぽい」型をHaskellに推論させたいのだ(簡単のため、相互再帰や二重再帰を考えないことにする)。ここで Rec (f Var) は なんとなく µx. f x という再帰型を表していると考えてほしい。 OCamlっぽく書くと ('a f as 'a) みたいな感じか。
追記:ここでは再帰型っぽく見えるようにする事しか考えていない。実際には x は Rec (F (G Var)) という再帰的でない構造のデータであることに注意.
関数依存(fundeps)を使う
これは関数依存で実現できる。
class RecFold u u' | u -> u' instance RecFold u u' => RecFold (F u) (F u') instance RecFold u u' => RecFold (G u) (G u') instance RecFold (Rec u) Var rec :: RecFold u u' => u -> Rec u' rec = undefined
のように、Recに対してVar を割り当ててくれる型関数っぽいものを fundepsで作ればよい(undefinedを含まないバージョンは後で貼ります)。
これで十分にうまくいく。
*Main> :t x x :: Rec (F (G Var))
型族 (type families) を使った方法 ()
最近の型レハッカーの間ではきっと type families が流行のはずなので、上のfundepsの実装をそのまま type families で実装してみた。
type family RecFoldT u :: * type instance RecFoldT (F u) = F (RecFoldT u) type instance RecFoldT (G u) = G (RecFoldT u) type instance RecFoldT (Rec u) = Var rec :: u -> Rec (RecFoldT u) rec = undefined
やってることは fundepsのバージョンと同じだ。がしかし、これで x = rec (F (G x)) の型を調べると、次の型エラーが出る:
*Main> :t let x = rec (F (G x)) in x <interactive>:1:4: Couldn't match expected type `u' against inferred type `Rec (RecFoldT (F (G u)))' In the expression: rec (F (G x)) In the definition of `x': x = rec (F (G x)) In the expression: let x = rec (F (G x)) in x
おわかりだろうか。
- fundeps バージョンでは
- 式の右辺に出現するxの型(これをuとしよう) と xの式全体から推論される型 RecFold u u' => Rec (F (G u')) がある。
- u と Rec (F (G u')) を単一化することで context reduction が働き、 Rec (F (G Var)) という型が推論される
- 式の右辺に出現するxの型(これをuとしよう) と xの式全体から推論される型 RecFold u u' => Rec (F (G u')) がある。
- いっぽう type families バージョンでは、
- xの型u と、xの右辺式全体の型 Rec (RecFoldT (F (G u))) を単一化して欲しいところだが、
- この単一化が許可されていないのか 型エラーになっている。
- xの型u と、xの右辺式全体の型 Rec (RecFoldT (F (G u))) を単一化して欲しいところだが、
考えたこと
- これはエラーか? コンパイラのバグか? 未定義(または実装者に予想外)の挙動か?
- エラーなら、どういう条件を破ったためエラーなのか? その条件がないとどうなるのか?(停止しなくなる?)
- バグなら報告
- 未定義なら報告
コード
{-# LANGUAGE TypeOperators, KindSignatures, ScopedTypeVariables, EmptyDataDecls #-} {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE NoMonomorphismRestriction #-} data F u = F u data G u = G u data Rec u = Rec u data Var = Var {- -- エラーになる class RecFold u where type RecFoldT u :: * recfold :: u -> RecFoldT u instance RecFold u => RecFold (F u) where type RecFoldT (F u) = F (RecFoldT u) recfold (F u) = F (recfold u) instance RecFold u => RecFold (G u) where type RecFoldT (G u) = G (RecFoldT u) recfold (G u) = G (recfold u) instance RecFold (Rec u) where type RecFoldT (Rec u) = Var recfold (Rec _) = Var -} class RecFold u u' | u -> u' where recfold :: u -> u' instance RecFold u u' => RecFold (F u) (F u') where recfold (F u) = F (recfold u) instance RecFold u u' => RecFold (G u) (G u') where recfold (G u) = G (recfold u) instance RecFold (Rec u) Var where recfold (Rec u) = Var rec x = Rec (recfold x) -- recursive structure x = rec (F (G x))
*1:Occurs check: cannot construct the infinite type: u = F (G u) となる