我正在解析表格
的一些陈述v1 = expression1
v2 = expression2
...
我正在使用State Monad,我的状态应该是一对(String,Expr a),我真的坚持要输入表达式。我尝试将状态实现为[PPair],我通过GADT定义PPair:
data PPair where
PPair :: (String, Expr a) -> PPair
一旦这条线通过了编译器,我觉得我做的事情确实非常错误。我压抑了这个想法并继续编码。当我编写将从State中提取变量值的代码时,我意识到了这个问题:
evalVar k ((PPair (kk, v)):s) = if k == kk then v else evalVar k s
我明白了:
Inferred type is less polymorphic than expected
这是非常期待的。我该如何解决这个问题?我知道我可以通过打破所有候选类型a的类型来解决它,但是没有更简洁的方法吗?
答案 0 :(得分:9)
问题在于evalVar
没有可能的类型:
evalVar :: String -> [PPair] -> Expr ?
您不能说?
是a
,因为您声称您的返回值适用于任何值a
。但是,您可以做的是将“具有未知类型的Expr
”包装到其自己的数据类型中:
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
(当然,您可以使用[(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包;您可以从Key
和ST
构建IO
,并使用Vault
作为异构容器。它基本上类似于Map
,其中键包含相应值的类型。具体来说,我建议将Var a
定义为Key (Expr a)
并使用Vault
而不是[PPair]
。 (完全披露:我已经参与了金库包。)
当然,您仍然需要将变量名称映射到Key
值,但是您可以在解析后立即创建所有Key
,并将它们带到周围而不是字符串。 (但是,使用此策略从Var
转换到相应的变量名称会有一些工作;您可以使用存在列表来执行此操作,但解决方案太长而无法放入此答案。)
(顺便说一句,你可以使用GADT为数据构造函数提供多个参数,就像常规类型一样:data PPair where PPair :: String -> Expr a -> PPair
。)