【发布时间】: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
AST 的reduce 的行为将评估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 Int和data 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