我一直在研究是否可以制作一个由操作和叶节点组成的非常简单的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的想法。但是,为Operation(最后一行)定义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岁时那样解释答案,我将不胜感激。
如果事实证明不可能评估这样的树,那很好,但是我非常想知道它背后的逻辑是什么。谢谢!
最佳答案
在顶级函数中添加类型签名:
eval :: Tree l -> l
通常认为这是一种好习惯,但这对于GADT尤其重要,因为不能通过其他方式推断出该类型。