【问题标题】:Could not deduce (b ~ a)无法推断 (b ~ a)
【发布时间】:2014-06-13 14:13:08
【问题描述】:

所以我有以下代码,我正在尝试为解释器编写一个抽象语法树,并且我不想将所有内容都塞进同一个 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 

-- ...

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

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

ASTreduce 的行为将评估AST,偶尔返回不同类型的AST,有时返回相同类型的AST

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

我最初选择 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。我不想开始在Eithers 和Maybes 之间折腾。这就是我上次最终做的事情,一团糟,我放弃了尝试在 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 中实现抽象语法树的有趣阅读材料。

【问题讨论】:

  • 我认为a 必须是一个 AST 才能......成为一个 AST。我不确定这是否是您想要的;)
  • 我应该说我对 Haskell 非常缺乏经验,所以如果这个问题对于更有经验的 Haskell 程序员来说似乎很简单,我深表歉意。
  • @BartekBanachewicz 你的意思是像AST a -> Env -> AST b
  • 这是因为您说任何AST a 都可以直接转换为AST b,这显然不能由编译器自动完成。如果我有data AST1 = Add Int Int | Mul Int Intdata AST2 = Function String [AST2] | Lit Int,那么reduce 的这个定义表明reduce (Function "subtract" [Lit 1, Lit 2]) some_environment 可以直接转换为AST1,反之亦然。你如何想象这会发生?仅供参考:forall a. AST a => Expr a 是 Haskell 中的反模式,我建议使用简单的 ADT 甚至 GADT 来实现更复杂的行为。
  • 继续... 那么reduce (Function "subtract" [Lit 1, Lit 2]) some_environment :: AST1 会是什么?您将如何对所有 AST 类型进行编码?这意味着您的构造存在问题,并且您正在从过多的 OOP 策略中解决问题。 Haskell 类型类不是接口,你不能从一个实例转换到另一个实例。

标签: haskell


【解决方案1】:

您难以理解错误消息的哪一部分?我觉得很清楚。

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 的风格并重新评估您的方法。

【讨论】:

  • 嗯,我想我将我的类型签名解释为它可能是另一个AST,我真的不认为这意味着我不能返回相同的@987654339 @.
  • 是的,我认为我的类型类存在量化方法可能不是最理想的方法。但是考虑一个 AST 之类的数字可能会减少到自身,其中作为复合 AST(my-funciton 2 2)((lambda (a b) (+ a b)) 2 2) 到复合表达式以外的值,如果它们有类似 (2 2 2) 的值,它将是语言上的错误。我认为可能很难将其表示为一种类型:/ 我在编辑中提到了更多。
  • 我猜你的reduce :: Env -> AST a -> Either (AST a) (AST b) 从技术上回答了这个问题,但我想避免我必须与 Either 匹配的情况......但我觉得应该有一个更优雅的解决方案
  • 我同意添加 EitherMaybe 会使事情变得不必要地复杂化。从您的编辑看来,GADTs 正是您所需要的。特别是递归类型:data Expr a = Compound [Expr a] | Ref String | Var a
  • 公平地说,我一直在阅读 AST 似乎很难输入,所以我想我必须有点创意:P 我肯定会研究 GADT,我'经过一番探索,我会更新这个问题
【解决方案2】:

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

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

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

如果您的实现返回一个类型为 a 的值,那么只有当 b 始终等于 a 时才会出现这种情况,这是编译器在报告无法证明 b ~ a 时所处理的问题。

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

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

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

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

【讨论】:

    【解决方案3】:

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

    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
    

    【讨论】:

    • 嗯,你是不是复制签名失败了? reduce :: AST b => a -> Env -> b
    • reduce 应该只取 2 个参数,我没看错吧?
    • @ABot 只是展示了一个可以工作的案例。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-19
    • 2019-02-03
    • 2017-01-11
    • 2023-03-19
    • 1970-01-01
    • 2021-11-28
    相关资源
    最近更新 更多