简体   繁体   中英

How to fold a tree without making it an instance of Foldable?

I've defined my tree as follows:

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

and made it an instance of a Functor:

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

now I want to define the following function to fold the tree:

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

I've tried something like this but I don't think it works since Tree isn't a monoid:

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

I'm having some trouble thinking how to do it. Any help would be appreciated.

First note that foldTree has three arguments:

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

Actually, convention with folds is to put the function argument first, so I'll in the following discuss foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b .

Now your attempt

foldTree f Tip = Tip

is lacking an argument, and the result type also doesn't make sense: Tip is a tree(-leaf), but you want the result to be simply b .

A good way to write functions where you have difficulty seeing what's supposed to happen is to let the compiler help you. Again, foldTree has three arguments, so in general its definition should have the form

foldTree q r t = _

where t is the tree, so the first clause will be

foldTree q r Tip = _

Well, you can actually present GHC (>= 7.8) with this “definition” . This is what it replies:

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 = _

So, it wants a result of type b . It so happens that you have a value of type b at hand, namely r . So one definition for this clause is

foldTree q r Tip = r

In fact that's the correct defintion and the only possible , since the only other thing you have at that point is the function q , but that would require an a value to evaluate, of which you have none.

So, let's move on:

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

gives

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) = _

Ok, so again you need a b . You could of course yet again simply return r , but that would mean the function simply ignores the entire tree. In particular, now you have a value x :: a which you could pass to q – sounds like a good idea. The result of q actually has type b , so let's try to use that as the result of the entire clause.

q has three arguments, I'm lazy so I'll again leave typed holes first...

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

giving

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)

Ok, one of these is clear-cut: we have a value x :: a , so that's the natural fit for _q₁ .

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

For _q₀ and _q₂ we need once more values of type b . We could in principle pass r to both of these, but that would mean we still don't use the subtrees lsub and rsub . How could we use those? Well, there's one function in the hint that eats trees: foldTree . Indeed it also yields b as the result. So looks like we need recursive calls here. Again use holes for the missing arguments:

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)

aha, so that leads us to

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

...

Found hole ‘_₁’ with type: b

Ok, now we've used everything else, so we might as well just plug in the initial value.

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

and so on, just fill the gaps with what GHC offers you with the right type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM