Existential Types

Existential typeとは、(1)のようにforallがデータコンストラクタの前について定義されるデータ型のことをいう*1。(2)とは違って、forallで修飾された型変数ができあがった型のパラメータになっていないことに注意して欲しい。
(1)

data Proc i o = forall b c. Proc (i -> b) (b -> c) (c -> o)
data Obj = forall a. Show a => Obj a

(2)

data ProcX i b c o = ProcX (i -> b) (b -> c) (c -> o)
data Show a => ObjX a = ObjX a

Existential typeの定義は、Objを例に取ると、このように読める。
任意の(forall)Showクラスに属す型aに対し、変数xの型がaであれば、Obj xはObjという型になる」
また、逆にコンストラクタを外すことを考えると、
「yの型がObjであれば、Showクラスに属する型aが何かあって(exists)、y@(Obj x)のxの型はaである」
ということがわかる。前者はデータコンストラクタを中心においた理解で、後者はできあがった型とはどういうものかという理解だといえるだろう。後者の考え方*2がexistential typeという名前の由来である。

データコンストラクタの型

Proc :: forall i o b c. (i -> b) (b -> c) (c -> o)
Obj :: forall a. Show a => a -> Obj a

Procは複数の手続きを一体にまとめて外に途中の型を漏らさないという例である。

runProc :: i -> o
runProc (Proc f g h) = h . g . f
testProc1 :: Proc Int Int
testProc1 = Proc (* 10) show length
testProc2 :: Proc Int Int
testProc2 = Proc (+ 1) (* 2) (`div` 8)

Objのようにtype classと併用することも多い。同じクラスに属するオブジェクトの型を区別しないで扱うことができる。以下はheterogeneous*3なリストを作れるという例である。x :: Objからx@(Obj y)の中身yについてわかることは、型がShowクラスに属するということだけである。したがってyに対してできることは「Showクラスに属する型の変数になら必ずできること」に制限されている。

objs = [Obj 1, Obj "str", Obj True]
testObj = map f objs where f (Obj y) = show y

Type classを用いず、直接「メソッド」を持たせて「オブジェクト」を作ることもできる。

data Obj = forall a. Obj a (a -> Bool)
bools = [f x | (Obj x f) <- [Obj 3 even, Obj 'c' isUpper]]

応用(Dependent types)

以下のように、実行時に値から型を作ることもできる。これは静的チェック時には型情報が外にもれないことを利用している。

type Vector1 = Double
type Vector2 = (Double, Double)

data AVec = forall v. Vec v => AVec v

-- Value to Types
tovec :: [Double] -> AVec
tovec [x]   = AVec x
tovec [x,y] = AVec (x, y)

-- Types to Values
apply :: (forall v. Vec v => v -> r) -> AVec -> r
apply f (AVec v) = f v

-- Main program
prog :: Vec v => v -> String
prog = show

class Show v => Vec v where
instance Vec Double where
instance Vec (Double,Double) where
*Dependent> apply prog $ tovec [1,2]
"(1.0,2.0)"
*Dependent> apply prog $ tovec [1]
"1.0"

ここではtovecを[x], [x,y]に対してしか定義していないが、Vecが再帰的に定義されるtype classであれば、tovecで再帰することで一般の次元に対して対応するVectorの型を得ることができる。

*1:詳しくはghcのUser's Guideなどを参照

*2:とその逆。Obj :: (exists a. Show a => a) -> Obj

*3:要素となるオブジェクトの型が同一ではない