无法推断(b~a)

时间:2014-06-13 14:13:09

标签: haskell

所以我有以下代码,我正在尝试为解释器编写一个抽象语法树,&我不想在相同的data类型中阻塞所有内容,因此我将编写一个具有基本行为的类型类(在本例中为AST)。

{-# LANGUAGE ExistentialQuantification #-}

import qualified Data.Map as M

-- ...    

data Expr = forall a. AST a => Expr a
type Env = [M.Map String Expr]

class AST a where
  reduce :: AST b => a -> Env -> b
  -- when i remove the line below, it compiles fine
  reduce ast _ = ast 

-- ...

当我删除类型类reduce中的AST的默认实现时,它编译得很好,但是当我提供一个返回它自己的实现时它就会抱怨。我得到以下编译器错误

src/Data/AbstractSyntaxTree.hs:13:18:
  Could not deduce (b ~ a)
  from the context (AST a)
    bound by the class declaration for `AST'
    at src/Data/AbstractSyntaxTree.hs:(11,1)-(13,20)
  or from (AST b)
    bound by the type signature for reduce :: AST b => a -> Env -> b
    at src/Data/AbstractSyntaxTree.hs:12:13-36
    `b' is a rigid type variable bound by          
        the type signature for reduce :: AST b => a -> Env -> b
        at src/Data/AbstractSyntaxTree.hs:12:13
    `a' is a rigid type variable bound by
        the class declaration for `AST'
        at src/Data/AbstractSyntaxTree.hs:11:11
  In the expression: ast
  In an equation for `reduce': reduce ast _ = ast

AST reduce的行为将评估AST,偶尔会返回不同类型的AST,有时会返回相同类型的AST }。

编辑:关于data Expr = forall a. AST a => Expr a& GADTŠ

我最初选择了data Expr = forall a. AST a => Expr a,因为我想代表这样的类型。

(+ 2 true) -> Compound [Expr (Ref "+"), Expr 2, Expr true]

(+ a 2) -> Compound [Expr (Ref "+"), Expr (Ref "a"), Expr 2]

(eval (+ 2 2)) -> Compound [Expr (Ref "eval"), 
                     Compound [
                       Expr (Ref "+"), 
                       Expr 2, 
                       Expr 2]]

((lambda (a b) (+ a b)) 2 2) -> Compound [Expr SomeLambdaAST, Expr 2, Expr 2]

由于我正在从文本生成AST,我觉得在GADT中表示严格类型的AST是一种负担,尽管我确实看到它们在Haskell中像DSL一样有用。

但是因为我从文本生成AST(可能包含上面的一些例子),所以可能有点难以预测我最终会得到什么AST。我不想开始在Either s&之间玩杂耍。 Maybe秒。这就是我上次最后做的事情。这是一团糟,&我放弃了尝试在Haskell中尝试这个。

但同样我不是最有经验的Haskell程序员,所以也许我正在以错误的方式看待这个,也许我可以用更严格的打字来实现AST,所以我会看一看,看看能否拿出GADT,但我有疑虑和我觉得它可能会以上次的方式结束。

最终我只是试着通过一个有趣的完成项目来学习Haskell,所以我不介意我的第一个Haskell项目是不是真正惯用的Haskell。获得一些工作是一个更高的优先事项,因此我可以绕过语言,并有一些东西可以显示它。

更新

我已经拍摄了@ cdk& @bheklilr建议并放弃了存在主义类型,虽然我使用了更简单的类型,而不是使用GADT(也是@ cdk& @bheklilr建议)。它可能是一个更强大的类型,但我只是想要熟悉Haskell,所以我放弃了几个小时后放弃了使用简单的数据类型:P

import qualified Data.Map as M

type Environment = [M.Map String AST]

data AST
  = Compound [AST]
  | FNum Double
  | Func Function
  | Err  String
  | Ref  String

data Function
  = NativeFn ([AST] -> AST)
  | LangFn   [String] AST

-- previously called reduce
eval :: Environment -> AST -> AST
eval env ast = case ast of
  Ref ref -> case (lookup ref env ) of
    Just ast -> ast
    Nothing  -> Err ("Not in scope `" ++ ref ++ "'")

  Compound elements -> case elements of
    []              -> Err "You tried to invoke `()'"
    function : args -> case (eval env function) of
      Func fn -> invoke env fn args
      other   -> Err $ "Cannot invoke " ++ (show other)

  _ -> ast

-- invoke & lookup are defined else where 

虽然我仍然可能会看到GADT,因为它们看起来非常有趣并且引导我阅读一些有关在haskell中实现抽象语法树的有趣阅读材料。

3 个答案:

答案 0 :(得分:4)

错误信息的哪一部分是您难以理解的?我认为这很清楚。

reduce的类型是

reduce :: AST b => a -> Env -> b

第一个参数的类型为a,GHC希望reduce返回b类型的内容,这可能与a完全不同。 GHC是正确的,抱怨您在预期a时尝试返回b的值。

“类型类的存在量化”是(如bheklilr所指出的)反模式。更好的方法是为AST

创建代数数据类型
data AST a

现在reduce变成了一个简单的函数:

reduce :: Env -> AST a -> AST b

如果您希望reduce能够返回其他类型的AST,则可以使用Either

reduce :: Env -> AST a -> Either (AST a) (AST b)

但我认为这不是你真正想要的。我的建议是看看GADT创建AST的风格并重新评估你的方法。

答案 1 :(得分:1)

您正在错误地解释此类型签名(以OO程序员常见的方式):

reduce :: AST b => a -> Env -> b

这并不意味着reduce可以选择它喜欢的任何类型(即AST的成员)并返回该类型的值。如果是这样,您的实施将是有效的。相反,它意味着对于调用者喜欢的任何类型b (即AST的成员),reduce必须能够返回该类型的值。 b很可能与有时相同,但它是来电者的选择,而不是选择减少。

如果你的实现返回一个类型为a的值,那么只有当b总是等于a时,这才是真的,这就是编译器在报告无法证明b ~ a时所处的内容。

Haskell没有子类型。类型变量不是可以实例化它们的所有具体类型的超类型,因为您可能习惯于在OO语言中使用Object或抽象接口类型。相反,类型变量是参数;任何声称具有参数类型的实现都必须无论选择哪种类型的参数。

如果你想使用一种设计,其中reduce可以在任何类型的AST中返回一个值(而不是任何类型的AST),那么你需要再次使用你的Expr盒,因为Expr是按其包含的AST类型进行参数化,但仍可包含任何AST:

reduce :: a -> Env -> Expr
reduce ast _ = Expr ast

现在,无论选择哪种类型的类型参数,reduce都可以工作,因为只有a。返回的Expr的消费者将无法限制Expr中的类型,因此无论该类型是什么,都必须编写它们。

答案 2 :(得分:0)

您的默认实现不会编译,因为它的定义错误。

reduce :: AST b => a -> b -> Env -> b
reduce ast _ = ast

现在ast的类型为a,reduce函数返回类型b,但根据您的实现,您返回的ast类型为a,但是编译器期望b

即便是这样的事情也会奏效:

reduce :: AST b => a -> b -> Env -> b
reduce _ ast _  = ast