如何评估Haskell中的这种通用抽象语法树?
How to evaluate this generic abstract syntax tree in Haskell?
我一直想看看我是否可以制作一个非常简单的 AST,它由操作和叶节点组成。但更具体地说,我希望能够使用任何类型作为叶节点,而不是像这样在 AST 数据类型本身中明确指定它。
-- Instead of this
data Tree = Number Int | Word String | Operation Tree (Tree -> Tree -> Tree) Tree
-- I'd like something along the lines of this
data Tree a = Leaf a | Operation Tree (Tree -> Tree -> Tree) Tree
这不一定很实用,但我想看看是否可行。到目前为止,我所管理的最接近的要求我摸索了一些 GADT 的概念:
{-# LANGUAGE GADTs #-}
data Tree l where
Leaf :: l -> Tree l
Operation :: Tree a -> (a -> b -> c) -> Tree b -> Tree c
let fivePlus2 = Operation (Leaf 5) (+) (Leaf 2)
eval (Leaf l) = l
eval (Operation left op right) = op (eval left) (eval right)
我的想法是我可以 运行 eval fivePlus2
,并得到 7。但是,为操作(最后一行)定义的 eval 导致以下非常模糊的错误:
<interactive>:187:38: error:
• Couldn't match type ‘a’ with ‘p’
‘a’ is a rigid type variable bound by
a pattern with constructor:
Operation :: forall a b c.
Tree a -> (a -> b -> c) -> Tree b -> Tree c,
in an equation for ‘eval’
at <interactive>:187:7-29
‘p’ is a rigid type variable bound by
the inferred type of eval :: Tree p -> p
at <interactive>:187:1-60
Expected type: Tree a -> a
Actual type: Tree p -> p
• In the first argument of ‘op’, namely ‘(eval left)’
In the expression: op (eval left) (eval right)
In an equation for ‘eval’:
eval (Operation left op right) = op (eval left) (eval right)
• Relevant bindings include
op :: a -> b -> p (bound at <interactive>:187:22)
left :: Tree a (bound at <interactive>:187:17)
eval :: Tree p -> p (bound at <interactive>:187:1)
<interactive>:187:50: error:
• Couldn't match type ‘b’ with ‘p’
‘b’ is a rigid type variable bound by
a pattern with constructor:
Operation :: forall a b c.
Tree a -> (a -> b -> c) -> Tree b -> Tree c,
in an equation for ‘eval’
at <interactive>:187:7-29
‘p’ is a rigid type variable bound by
the inferred type of eval :: Tree p -> p
at <interactive>:187:1-60
Expected type: Tree b -> b
Actual type: Tree p -> p
• In the second argument of ‘op’, namely ‘(eval right)’
In the expression: op (eval left) (eval right)
In an equation for ‘eval’:
eval (Operation left op right) = op (eval left) (eval right)
• Relevant bindings include
right :: Tree b (bound at <interactive>:187:25)
op :: a -> b -> p (bound at <interactive>:187:22)
eval :: Tree p -> p (bound at <interactive>:187:1)
老实说,我完全不确定这意味着什么,而且我在这里已经超出了我的理解范围,最初是在 F# 中尝试过的,但发现它的表现力不够。我已经有一段时间没有进行函数式编程了,并且是 Haskell 的初学者,所以如果答案像我 5 岁时那样解释,我将不胜感激。
如果事实证明无法评估这样一棵树,那很好,但我非常想知道它背后的逻辑是什么。谢谢!
向 top-level 函数添加类型签名:
eval :: Tree l -> l
这通常被认为是好的做法,但这对于 GADT 尤其重要,因为否则无法推断出该类型。
我一直想看看我是否可以制作一个非常简单的 AST,它由操作和叶节点组成。但更具体地说,我希望能够使用任何类型作为叶节点,而不是像这样在 AST 数据类型本身中明确指定它。
-- Instead of this
data Tree = Number Int | Word String | Operation Tree (Tree -> Tree -> Tree) Tree
-- I'd like something along the lines of this
data Tree a = Leaf a | Operation Tree (Tree -> Tree -> Tree) Tree
这不一定很实用,但我想看看是否可行。到目前为止,我所管理的最接近的要求我摸索了一些 GADT 的概念:
{-# LANGUAGE GADTs #-}
data Tree l where
Leaf :: l -> Tree l
Operation :: Tree a -> (a -> b -> c) -> Tree b -> Tree c
let fivePlus2 = Operation (Leaf 5) (+) (Leaf 2)
eval (Leaf l) = l
eval (Operation left op right) = op (eval left) (eval right)
我的想法是我可以 运行 eval fivePlus2
,并得到 7。但是,为操作(最后一行)定义的 eval 导致以下非常模糊的错误:
<interactive>:187:38: error:
• Couldn't match type ‘a’ with ‘p’
‘a’ is a rigid type variable bound by
a pattern with constructor:
Operation :: forall a b c.
Tree a -> (a -> b -> c) -> Tree b -> Tree c,
in an equation for ‘eval’
at <interactive>:187:7-29
‘p’ is a rigid type variable bound by
the inferred type of eval :: Tree p -> p
at <interactive>:187:1-60
Expected type: Tree a -> a
Actual type: Tree p -> p
• In the first argument of ‘op’, namely ‘(eval left)’
In the expression: op (eval left) (eval right)
In an equation for ‘eval’:
eval (Operation left op right) = op (eval left) (eval right)
• Relevant bindings include
op :: a -> b -> p (bound at <interactive>:187:22)
left :: Tree a (bound at <interactive>:187:17)
eval :: Tree p -> p (bound at <interactive>:187:1)
<interactive>:187:50: error:
• Couldn't match type ‘b’ with ‘p’
‘b’ is a rigid type variable bound by
a pattern with constructor:
Operation :: forall a b c.
Tree a -> (a -> b -> c) -> Tree b -> Tree c,
in an equation for ‘eval’
at <interactive>:187:7-29
‘p’ is a rigid type variable bound by
the inferred type of eval :: Tree p -> p
at <interactive>:187:1-60
Expected type: Tree b -> b
Actual type: Tree p -> p
• In the second argument of ‘op’, namely ‘(eval right)’
In the expression: op (eval left) (eval right)
In an equation for ‘eval’:
eval (Operation left op right) = op (eval left) (eval right)
• Relevant bindings include
right :: Tree b (bound at <interactive>:187:25)
op :: a -> b -> p (bound at <interactive>:187:22)
eval :: Tree p -> p (bound at <interactive>:187:1)
老实说,我完全不确定这意味着什么,而且我在这里已经超出了我的理解范围,最初是在 F# 中尝试过的,但发现它的表现力不够。我已经有一段时间没有进行函数式编程了,并且是 Haskell 的初学者,所以如果答案像我 5 岁时那样解释,我将不胜感激。
如果事实证明无法评估这样一棵树,那很好,但我非常想知道它背后的逻辑是什么。谢谢!
向 top-level 函数添加类型签名:
eval :: Tree l -> l
这通常被认为是好的做法,但这对于 GADT 尤其重要,因为否则无法推断出该类型。