generated at
TypeFamilyを型レベル関数として見る
Type Familyは型の族ではあるが、型レベルの関数と解釈することもできる
「関数」として見ることで、全域なのかどうかや、単射なのかどうかなどが重要になってくる


型族とは型の族のことで使った2つの型族を例に話をすすめる
hs
type family List x where List () = Int List Bool = (Int, Integer) List Double = [Double]
hs
{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} data Nat = Zero | Succ Nat type family Add n m where Add 'Zero n = n Add ('Succ n) m = Add n ('Succ m)
このノート内に登場する List は自作した型族のことであって
GHC.Typesで提供される List == [] のやつとは全く別の概念であることに注意mrsekut


TypeFamilyを「型レベル関数」と見ることもできる
引数が型で、戻り値が型であるような型のことを「型レベル関数」と言っているわけだが、
(型族ではない通常の)genericな型 Maybe a なども型レベル関数であるので、
「型族とは型レベル関数のことである」と言うのは正しいが、かなり雑な説明である


通常の「 Maybe a のような型レベル関数」と、「型族のような型レベル関数」の違いはなにか
それはパラメトリック多相な関数と、Ad Hoc多相な関数の違いに似ている
雑に言えば前者はgenericsのことで、後者はoverloadのことである
対応
似てる今回の例
通常のパラメータ化された型コンストラクタパラメトリック多相関数(generics)Maybe型
型族コンストラクタアドホック多相関数(overlaod)List型族
前者の方は、例として Maybe a などがあるが、
a に任意の型 Hoge を代入することで、 Maybe Hoge という型が作られる
型表記っぽく書くと Maybe :: a -> Maybe a
これは単一の型の実装 Maybe _ が、複数の引数に対応していることを表す
つまり、全ての型 a に対して同じデータ表現を示す
後者の方は、例として上で定義したような List があるが、
() -> Int だとか Bool -> (Int, Integer) だとかで定義される異なる型関数を、
List という同じ名前で呼んでいる
つまり、雑に言えば、
前者の型関数 Maybe の返り値は、必ず Maybe 型となるが、
後者の型関数 List の返り値は、 Int になったり、 [Double] になったりと、別の型になりうる


とは言え、型族を以下のように定義をすれば、パラメトリック多相関数と同じものになる
無限にこう定義するとか(無理だが)
hs
type family Maybe' x where Maybe' Int = Maybe' Int Maybe' String = Maybe' String ...
こうとか
hs
type family Maybe' x where Maybe' a = Maybe' a
と書いた場合、以下と同じ意味になる
hs
type Maybe' a = Maybe' a
この場合、type familyで定義する理由は全く無い
寧ろ、プログラムの読者に無駄な意図を与えてしまう



単射かどうか
型関数として見た場合の差異として単射かどうかがある
Maybe のような通常の型関数の場合、これは単射である
hs
data Maybe a = Nothing | Just a
つまり、 Maybe a ~ Maybe b ならば a ~ b である
というかまあ全単射だよねmrsekut
List などの型族な型関数の場合、これは単射で無くても良い
例えば以下のような定義もできうる
hs
type instance List Int = Bool type instance List Char = Bool
つまり、 List a ~ List b であっても a ~ b とは限らない






全域関数か部分関数か
型関数として見た場合に List Add の差異はどこにあるか
List は、3つのtype indexを持っていて、
Add は、無限個のtype indexを持っていた
この関係を関数として見ると、
List は、部分関数 * -> * であり、
Add は、全域関数 Nat -> Nat -> Nat である
つまり、
List の引数は「kindが * な型」だが、「kindが * 何でも良い」わけではない
実際、 List String もkind的にはokなはずだが、未定義なためerrorになる
これは部分関数の性質である
Add の引数は「kindが Nat な型であり、kindが Nat ならば全て定義されている
これは全域関数の性質である