有限数の要素を持つリストを表す型を作りたいと思います。
これを行う簡単な方法は、厳密な評価です。
data FiniteList a
= Nil
| Cons a !(List a)
これで無限のリストは下に相当します。
ただし、そのようなリストの作成を完全に防止するタイプが必要です。理想的には、無限リストを作成しようとすると、コンパイル時エラーが発生するようにしたいと思います。
GADTs
とDataKinds
を使用してサイズのリストを作成すると、これがどのように行われるかがわかります。
data Natural = Zero | Succ Natural
data DependentList :: Natural -> Type -> Type where
Nil :: DependentList 'Zero a
Cons :: a -> DependentList n a -> DependentList ('Succ n) a
次のようなものを作成して
a = Cons 1 a
これには型が必要なので、型エラーが発生しますn ~ 'Succ n
。
これの問題は、それが単一のリストタイプではなく、リストのサイズごとに1つのタイプのクラスであることです。したがって、たとえば、このリストにtake
またはdrop
のバージョンを記述したい場合は、深刻な依存型入力を開始する必要があります。
コンパイル時に有限性を強制する単一の型の下でこれらの個別の型をすべて統合したいと思います。
これはできますか?
"ゴーストオブディパーテッドプルーフズ" アプローチもあります。これには、慎重に公開されたスマートコンストラクターを備えたタグ付きnewtypeが含まれ、type-tagで継続ポリモーフィックを伴う継続渡しスタイルで機能します。
{-# LANGUAGE DeriveFunctor, RankNTypes, RoleAnnotations #-}
module FinList (FinList,empty,toFinList,cons) where
-- FinList constructor should NOT be public, or else everything breaks!
newtype FinList name a = FinList { getFinList :: [a] } deriving Functor
-- Don't allow Data.Coerce.coerce to turn FinList X a into forall x. FinList x a
type role FinList nominal representational
empty :: forall a r. (forall name . FinList name a -> r) -> r
empty f = f (FinList [])
toFinList:: forall a r. Int -> [a] -> (forall name. FinList name a -> r) -> r
toFinList n as f = f (FinList (take n as))
cons :: forall a r name'. a -> FinList name' a -> (forall name. FinList name a -> r) -> r
cons a (FinList as) f = f (FinList (a:as))
これにより、FinList
モジュールのクライアントが循環定義を作成できなくなります。