如何折叠树而不使其成为可折叠的实例?

时间:2017-01-08 23:13:10

标签: haskell tree functional-programming fold

我已按如下方式定义了我的树:

data Tree a
  = Tip
  | Node (Tree a) a (Tree a)
  deriving Show

并使其成为Functor的一个实例:

instance Functor Tree where
  fmap f Tip                       = Tip
  fmap f (Node leftsub x rightsub) = Node (fmap f leftsub) (f x) (fmap f rightsub)

现在我想定义以下函数来折叠树:

foldTree :: b -> (b -> a -> b -> b) -> Tree a -> b

我尝试过类似的东西,但我认为它不起作用,因为Tree不是幺半群:

foldTree f z Tip = Tip
foldTree f z (Node leftsub x rightsub) = foldr f leftsub ++ f x ++ foldr f rightsub

我在思考如何做到这一点时遇到了一些麻烦。任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:8)

首先请注意foldTree三个参数:

foldTree :: b                   -- ^ Starting value
         -> (b -> a -> b -> b)  -- ^ Folding function
         -> Tree a              -- ^ Tree to fold over
         -> b

实际上,带折叠的约定是将函数参数放在第一位,所以我将在下面讨论foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b

现在你的尝试

foldTree f Tip = Tip

缺少参数,结果类型也没有意义:Tip是树(-leaf),但您希望结果只是b

编写函数的好方法是让你很难看到应该发生的事情是让编译器帮助你。同样,foldTree三个参数,所以一般来说它的定义应该是

形式
foldTree q r t = _

其中t是树,因此第一个子句将是

foldTree q r Tip = _

好吧,you can actually present GHC (>= 7.8) with this “definition”。这是它回复的内容:

Found hole ‘_’ with type: b
Where: ‘b’ is a rigid type variable bound by
           the type signature for
             foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b
           at /tmp/wtmpf-file26763.hs:6:13
Relevant bindings include
  r :: b (bound at /tmp/wtmpf-file26763.hs:8:12)
  q :: b -> a -> b -> b (bound at /tmp/wtmpf-file26763.hs:8:10)
  foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b
    (bound at /tmp/wtmpf-file26763.hs:8:1)
In the expression: _
In an equation for ‘foldTree’: foldTree q r Tip = _

因此,它需要类型b的结果。碰巧您手头有b类型的值,即r。因此,该子句的一个定义是

foldTree q r Tip = r

事实上,这是正确的定义,只有可能的,因为此时你唯一的另一件事就是函数q,但这需要a要评估的价值,你没有。

所以,让我们继续前进:

foldTree _ r Tip = r
foldTree q r (Node lsub x rsub) = _

给出

Found hole ‘_’ with type: b
Where: ‘b’ is a rigid type variable bound by
           the type signature for
             foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b
           at /tmp/wtmpf-file26763.hs:6:13
Relevant bindings include
  rsub :: Tree a (bound at /tmp/wtmpf-file26763.hs:9:27)
  x :: a (bound at /tmp/wtmpf-file26763.hs:9:25)
  lsub :: Tree a (bound at /tmp/wtmpf-file26763.hs:9:20)
  r :: b (bound at /tmp/wtmpf-file26763.hs:9:12)
  q :: b -> a -> b -> b (bound at /tmp/wtmpf-file26763.hs:9:10)
  foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b
    (bound at /tmp/wtmpf-file26763.hs:8:1)
In the expression: _
In an equation for ‘foldTree’: foldTree q r (Node lsub x rsub) = _

好的,你需要一个b。你当然可以再次简单地返回r,但这意味着该函数只是忽略整个树。特别是,现在您拥有x :: a,您可以传递给q - 这听起来不错。 q的结果实际上具有类型b,所以让我们尝试将其用作整个子句的结果。

q有三个参数,我很懒,所以我会再次先打出打孔......

foldTree q r (Node lsub x rsub) = q _q₀ _q₁ _q₂

Found hole ‘_q₀’ with type: b
...
Found hole ‘_q₁’ with type: a
...
Found hole ‘_q₂’ with type: b
...
Relevant bindings include
  rsub :: Tree a (bound at /tmp/wtmpf-file26763.hs:9:27)
  x :: a (bound at /tmp/wtmpf-file26763.hs:9:25)
  lsub :: Tree a (bound at /tmp/wtmpf-file26763.hs:9:20)
  r :: b (bound at /tmp/wtmpf-file26763.hs:9:12)
  q :: b -> a -> b -> b (bound at /tmp/wtmpf-file26763.hs:9:10)
  foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b
    (bound at /tmp/wtmpf-file26763.hs:8:1)

好的,其中一个是明确的:我们有一个值x :: a,所以这是_q₁的自然拟合。

foldTree q r (Node lsub x rsub) = q _q₀ x _q₂

对于_q₀_q₂,我们需要再次输入b类型的值。我们原则上可以将r传递给这两者,但这意味着我们仍然不使用子树lsubrsub。我们怎么能用那些?嗯,提示中有一个功能就是吃树:foldTree。实际上它也会产生b。所以看起来我们需要递归调用。再次为缺少的参数使用漏洞:

foldTree q r (Node lsub x rsub)
        = q (foldTree _₀ _₁ lsub) x (foldTree _₂ _₃ rsub)

...

Found hole ‘_₀’ with type: b -> a -> b -> b
...
Relevant bindings include
      q :: b -> a -> b -> b (bound at /tmp/wtmpf-file26763.hs:9:10)
啊哈,所以这导致我们

foldTree q r (Node lsub x rsub)
        = q (foldTree q _₁ lsub) x (foldTree _₂ _₃ rsub)

...

Found hole ‘_₁’ with type: b

好的,现在我们已经使用了其他所有内容,所以我们也可以插入初始值。

foldTree q r (Node lsub x rsub)
        = q (foldTree q r lsub) x (foldTree _₂ _₃ rsub)

等等,只需填写GHC为您提供的正确类型的空白。