这个问题被 Phil Wadler 命名为“表达问题”,用他的话来说:
目标是按案例定义数据类型,其中可以在数据类型上添加新案例并在数据类型上添加新函数,而无需重新编译现有代码,同时保留
静态类型安全。
拥有可扩展数据类型的一种解决方案是使用类型类。
作为一个例子,假设我们有一种简单的算术语言:
data Expr = Add Expr Expr | Mult Expr Expr | Const Int
run (Const x) = x
run (Add exp1 exp2) = run exp1 + run exp2
run (Mult exp1 exp2) = run exp1 * run exp2
例如
ghci> run (Add (Mult (Const 1) (Const 3)) (Const 2))
5
如果我们想以可扩展的方式实现它,我们应该切换到类型类:
class Expr a where
run :: a -> Int
data Const = Const Int
instance Expr Const where
run (Const x) = x
data Add a b = Add a b
instance (Expr a,Expr b) => Expr (Add a b) where
run (Add expr1 expr2) = run expr1 + run expr2
data Mult a b = Mult a b
instance (Expr a, Expr b) => Expr (Mult a b) where
run (Mult expr1 expr2) = run expr1 * run expr2
现在让我们扩展语言加减法:
data Sub a b = Sub a b
instance (Expr a, Expr b) => Expr (Sub a b) where
run (Sub expr1 expr2) = run expr1 - run expr2
例如
ghci> run (Add (Sub (Const 1) (Const 4)) (Const 2))
-1
有关此方法的更多信息,以及关于表达问题的一般信息,请查看 Ralf Laemmel 在 Channel 9 上的视频1 和 2。
但是,正如 cmets 中所注意到的,此解决方案改变了语义。例如,表达式列表不再合法:
[Add (Const 1) (Const 5), Const 6] -- does not typecheck
在功能珍珠"Data types a la carte" 中介绍了使用类型签名的联积的更通用的解决方案。另请参阅纸上的 Wadler 的comment。