リストの長さをハードコーディングする必要はありません。代わりに、次の型を定義できます。
data UList a where
UList :: Nat n => List n a -> UList a
どこ
class Nat n where
asInt :: n -> Int
instance Nat Z where
asInt _ = 0
instance Nat n => Nat (S n) where
asInt x = 1 + asInt (pred x)
where
pred = undefined :: S n -> n
そして私たちも持っています
fromList :: [a] -> UList a
fromList [] = UList Nil
fromList (x:rest) =
case fromList rest of
UList xs -> UList (Cons x xs)
この設定により、コンパイル時に長さが不明なリストを作成できます。case
パターンマッチを実行して存在ラッパーから型を抽出し、Nat
クラスを使用して型を整数に変換することで長さにアクセスできます。
コンパイル時に値がわからない型を持つことの利点は何だろうと思うかもしれません。答えは、型がどうなるか分からなくても、不変条件を適用できるということです。たとえば、次のコードは、リストの長さを変更しないことが保証されています。
mapList :: (a -> b) -> List n a -> List n b
そして、たとえば と呼ばれる型ファミリを使用した型追加がある場合、次のAdd
ように書くことができます
concatList :: List m a -> List n a -> List (Add m n) a
これにより、2 つのリストを連結すると 2 つの長さの合計を持つ新しいリストが得られるという不変条件が適用されます。