JoseJuan 和 MathematicalOrchid 讨论了在公开类型和接口的同时隐藏构造函数的关键点,但是在 Haskell 中还有另一种管理某些类型不变量的技术:在类型系统中对它们进行编码。代数数据类型在一定程度上是自行完成的:
data Tree a = Tri (Tree a) a (Tree a) a (Tree a)
| Bin (Tree a) a (Tree a)
| Tip
比
更受限制
newtype Tree a = Tree [a] [Tree a]
但您可以使用嵌套类型、幻像类型和 GADT 走得更远。例如,Data.Sequence 定义
newtype Elem a = Elem a
data Node a = Node2 !Int a a | Node3 !Int a a a
data Digit a = One a | Two a a | Three a a a | Four a a a a
data FingerTree a = Empty
| Single a
| Deep !Int (Digit a)
(FingerTree (Node a))
(Digit a)
newtype Seq a = Seq (FingerTree (Elem a))
请注意,深层FingerTree a 包含FingerTree (Node a)。这称为“嵌套”或“非常规”类型;它确保每一层的 2-3 棵树比上一层的树深一棵。
使用幻像类型和 GADT 可以以不同方式维护相同的形状不变量(但效率较低,事实证明):
{-# LANGUAGE GADTs, DataKinds, KindSignatures #-}
data Nat = Z | S Nat
-- This is a GADT; n is a phantom type
data Tree23 (n::Nat) a where
Elem :: a -> Tree23 Z a
Node2 :: !Int -> Tree23 n a ->
Tree23 n a -> Tree23 (S n) a
Node3 :: !Int -> Tree23 n a -> Tree23 n a ->
Tree23 n a -> Tree23 (S n) a
-- n is again a phantom type
data Digit (n::Nat) a
= One (Tree23 n a)
| Two (Tree23 n a) (Tree23 n a)
| Three (Tree23 n a) (Tree23 n a) (Tree23 n a)
| Four (Tree23 n a) (Tree23 n a) (Tree23 n a) (Tree23 n a)
-- n is still a phantom type
data FingerTree (n::Nat) a
= Empty
| Single a
| Deep !Int (Digit n a) (FingerTree (S n) a) (Digit n a)
在这个版本中,手指树的级别是使用幻像类型“跟踪”的,然后使用 GADT 强制 2-3 棵树的高度与之匹配。