问题是没有可能的类型evalVar
可以有:
evalVar :: String -> [PPair] -> Expr ?
你不能说?
is a
,因为你声称你的返回值适用于a
. 但是,您可以做的是将“Expr
具有未知类型的an”包装到它自己的数据类型中:
data SomeExpr where
SomeExpr :: Expr a -> SomeExpr
或者,等效地,使用RankNTypes
而不是GADTs
:
data SomeExpr = forall a. SomeExpr (Expr a)
这称为存在量化。然后,您可以PPair
使用以下方式重写SomeExpr
:
data PPair = PPair String SomeExpr
并evalVar
解决:
evalVar k (PPair kk v : xs)
| k == kk = v
| otherwise = evalVar k xs
(当然,您可以只使用 a[(String,SomeExpr)]
和标准lookup
函数。)
不过,一般来说,像这样试图在 Haskell 级别保持完全键入的表达式可能是徒劳的。像Agda这样的依赖类型语言不会有任何问题,但您可能最终会遇到 Haskell 无法很快完成的事情,或者将事情削弱到您想要的编译时安全性的程度丢失了。
当然,这并不是说它永远不会起作用。类型语言是 GADT 的激励示例之一。但它可能不会像你想要的那样工作,如果你的语言有任何非平凡的类型系统特性,比如多态,你可能会遇到麻烦。
如果你真的想保持打字,那么我会使用比字符串更丰富的结构来命名变量;有一个Var a
明确携带该类型的类型,如下所示:
data PPair where
PPair :: Var a -> Expr a -> PPair
evalVar :: Var a -> [PPair] -> Maybe (Expr a)
实现与此类似的一个好方法是使用Vault包;您可以从and构造Key
s ,并将其用作异构容器。它基本上就像键保存相应值的类型的地方。具体来说,我建议定义为并使用 a而不是你的. (完全披露:我已经研究过 Vault 包。)ST
IO
Vault
Map
Var a
Key (Expr a)
Vault
[PPair]
当然,您仍然必须将变量名称映射到Key
值,但您可以Key
在解析后立即创建所有 s,并携带它们而不是字符串。(不过,使用这种策略从 aVar
到其对应的变量名需要做一些工作;您可以使用存在列表来完成此操作,但解决方案太长而无法放入此答案。)
(顺便说一句,您可以为具有 GADT 的数据构造函数提供多个参数,就像常规类型一样:data PPair where PPair :: String -> Expr a -> PPair
。)