読者です 読者をやめる 読者になる 読者になる

Type Families

Haskell

HaskellのGHC拡張でType Families(型族)というのがあります。これはC++テンプレートメタプログラミングをやったことがある人にはなじみ深い、型を受け取って型を返す型関数(メタ関数)を実現する機能です。


たとえば、以下は型aを受け取って型aを返す型関数Identです:

{-# LANGUAGE TypeFamilies #-}

type family Ident a :: *
type instance Ident a = a

ident :: Ident a -> Ident a
ident x = x

main = do
        print $ ident 3
        print $ ident "hello"
3
"hello"

まず、型関数の宣言は、以下のように書きます。

type family Ident a :: *

Identというのが型関数名。aが引数の型。続く:: *というのは型関数の型です(省略可能)。1引数を受け取るので*を一つだけ書いています。2引数の場合は、関数の型のように* -> *と書きます。


その次の行が、型関数の中身です。

type instance Ident a = a

型関数Identが、aというあらゆる型を受け取って、受け取った型をそのまま返しています。

ident :: Ident a -> Ident a

この関数identは、コンパイル時に型関数Identが評価され、

ident :: a -> a

となります。


型関数のインスタンスを書く際には、具体的な型でオーバーロード(条件分岐)することができます。

type family Ident a :: *
type instance Ident Int = Int
type instance Ident Char = Int

このように書いた場合、Ident Intを呼び出すとIntという型が返され、Ident Charという型関数の呼び出しではCharではなくIntという型が返されます。

実際に使った例は以下:

{-# LANGUAGE TypeFamilies #-}

type family Ident a :: *
type instance Ident Int = Int
type instance Ident Char = Int

ident :: Ident Int -> Ident Int
ident x = x

ident' :: Ident Char -> Ident Char
ident' x = x

main = do
        print $ ident 3
        print $ ident' 3
3
3

ident'関数はIdent Charという型を受け取るのに、使う側では3というInt型の値を渡すことができています。これはIdent Charという型がIdent型関数の条件分岐によってInt型を示すからです。つまり、

ident' :: Ident Char -> Ident Char

関数ident'はIdent型関数にCharを渡して呼び出したことによって、以下のように置き換えられます:

ident' :: Int -> Int


Type Familiesは、型の単なる別名定義ではコードの重複をなくせない場合に使用するようです。型を分解するとかラップするとかいった操作に使うことになると思います。