所以我有以下代码,我正在尝试为解释器编写一个抽象语法树,&我不想在相同的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中实现抽象语法树的有趣阅读材料。
答案 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