我一直在研究是否可以制作一个由操作和叶节点组成的非常简单的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尤其重要,因为不能通过其他方式推断出该类型。

07-24 20:31