这个问题被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。
但是需要注意的是,正如评论所指出的那样,这个解决方案改变了语义。例如,表达式列表不再合法:
[Add (Const 1) (Const 5), Const 6]
在函数式珠玑"Data types a la carte"中,提出了一种使用类型签名的余积更普遍的解决方案。请参阅Wadler对该论文的评论。
data ExprE = forall a. Expr a => a
然后[ExprE]
展示了旧的行为。 - alternativedata ExprE = forall a. Expr a => E a
,否则我无法运行你的代码片段。能否像你的示例中那样避免这种情况? - Seymour Kooze