GADT用于多态列表

时间:2012-01-20 17:31:26

标签: haskell gadt

我正在解析表格

的一些陈述
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的类型来解决它,但是没有更简洁的方法吗?

1 个答案:

答案 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包;您可以从KeyST构建IO,并使用Vault作为异构容器。它基本上类似于Map,其中键包含相应值的类型。具体来说,我建议将Var a定义为Key (Expr a)并使用Vault而不是[PPair]。 (完全披露:我已经参与了金库包。)

当然,您仍然需要将变量名称映射到Key值,但是您可以在解析后立即创建所有Key,并将它们带到周围而不是字符串。 (但是,使用此策略从Var转换到相应的变量名称会有一些工作;您可以使用存在列表来执行此操作,但解决方案太长而无法放入此答案。)

(顺便说一句,你可以使用GADT为数据构造函数提供多个参数,就像常规类型一样:data PPair where PPair :: String -> Expr a -> PPair。)