3

This is a question from Chapter 11, Algebraic Datatypes of "Haskell Programming from first principles":

data BinaryTree a =
  Leaf
  | Node (BinaryTree a) a (BinaryTree a)
  deriving (Eq, Ord, Show)

We do not actually insert a value into an existing tree; each time we want to insert a value into the data structure, we build a whole new tree:

insert' :: Ord a => a -> BinaryTree a -> BinaryTree a
insert' b Leaf = Node Leaf b Leaf
insert' b (Node left a right)
  | b == a = Node left a right
  | b < a = Node (insert' b left) a right
  | b > a = Node left a (insert' b right)

This is a map function for the data structure of BinaryTree:

mapTree :: (a -> b) -> BinaryTree a -> BinaryTree b
mapTree _ Leaf = Leaf
mapTree f (Node left a right) = 
  Node (mapTree f left) (f a) (mapTree f right)

Write foldr for BinaryTree

Given the definition of BinaryTree we have provided, write a catamorphism for the binary trees.

-- any traversal order is fine
foldTree :: (a -> b -> b) 
  -> b 
  -> BinaryTree a 
  -> b

The type above is a hint for those that don’t convert the tree into a list before applying the folding function.

Rewrite map for BinaryTree

Using the foldTree you just wrote, rewrite mapTree using foldTree. The absence of an Ord constraint is intentional, you don’t need to use the insert function.

mapTree' :: (a -> b)
  -> BinaryTree a
  -> BinaryTree b
mapTree' f bt =
  foldTree undefined undefined undefined

I managed to get an answer that works for the first question about foldr with a lot of help from: https://github.com/johnchandlerburnham/hpfp/blob/master/11/BinaryTree.hs

My answer:

foldTree f b Leaf = b
foldTree f b (Node left a right) 
  = (foldTree f tempb left) where
    tempb = (f a) tempright
    tempright = foldTree f b right

However, for the second question about writing a new mapTree for BinaryTree, I could not find an answer to that. The original mapTree is provided above. Even the answer at the johnchandlerburnham link uses a different foldtree.

Could someone please help get a workable answer for the second question based on my answer to the first question? Or is another answer for the first question required?

A tree for testing could be:

testTree :: BinaryTree Integer
testTree =
  Node (Node Leaf 3 Leaf) 1 (Node Leaf 4 Leaf)
Will Ness
  • 70,110
  • 9
  • 98
  • 181
maxloo
  • 453
  • 2
  • 12
  • That does not look like a catamorphism to me. To have `cata` on trees, we should have a signature like `foldTree :: (b -> a -> b -> b) -> b -> BinaryTree a -> b`, with the additional `b ->` representing the left subtree. Are you sure about that signature? It looks like there is some confusion between `foldr` as in `Foldable` (which flattens the tree into a list) and `cata` which preserves the full structure (being the catamorphism). You need the latter for `mapTree` (it is called `foldTree1` in that file, IIUC). – chi Aug 23 '20 at 17:08
  • @chi: Thanks, I'm new to catamorphism, so I'm still feeling my way around this. Maybe the second question about mapping is wrong, is my answer to the first question ok? The book says that "catamorphism is a means of breaking down the structure of any datatype", and that's in another chapter about "Folding Lists". It then gives 3 examples I don't quite understand, 2 of which are `data Bool = False | True` `bool :: a -> a -> Bool -> a`, and `data Maybe a = Nothing | Just a` `maybe :: b -> (a -> b) -> Maybe a -> b`. – maxloo Aug 24 '20 at 00:42
  • 1
    @chi: The third example is `data Either a b = Left a | Right b` `either :: (a -> c) -> (b -> c) -> Either a b -> c`. The book just says "See if you can notice a pattern". Can you notice any pattern? – maxloo Aug 24 '20 at 01:05
  • @chi: I can only notice that the type constructor appears after the second 'general' -> in the 3rd 'term'. I hope I've used these words correctly. – maxloo Aug 24 '20 at 01:15
  • 1
    Understanding catamorphisms requires some effort. There are many ways to introduce them. One of them is: `cata` replaces constructor applications with arbitrary function applications. E.g. a value of type `Node Leaf 3 (Node Leaf 4 Leaf)` can be applied to `foldTree node leaf` and the result is (the evaluation of) `node leaf 3 (node leaf 4 leaf)`, where `node` and `leaf` are arbitrary arguments. To type check, we need a constant `leaf :: b` and a function `node :: b -> Int -> b -> b` as arguments for `foldTree` (possibly further generalizing `Int` to `a`). – chi Aug 24 '20 at 07:44
  • 1
    `maybe` does the same. `maybe n j` applied to `Nothing` outputs `n`, and applied to `Just x` outputs `j x` -- again, replacing constructors with arbitrary functions/constants. `foldr c n` similarly maps `1 : 2 : []` which is `(:) 1 ((:) 2 [])` to `c 1 (c 2 n)` and is a catamorphism for lists. – chi Aug 24 '20 at 07:46

1 Answers1

2

You can't write mapTree using a foldTree with that signature. (As @chi notes, the technical problem is that foldTree has the wrong signature to be a true catamorphism for BinaryTree.) In fact, if you load up that linked Haskell file BinaryTree.hs, you'll see that the mapTree' there doesn't work correctly:

λ> :l BinaryTree
λ> mapTree (+1) testTree
Node (Node Leaf 2 Leaf) 3 (Node Leaf 4 Leaf)
λ> mapTree' (+1) testTree
Node (Node (Node Leaf 3 Leaf) 2 Leaf) 4 Leaf

It gives the right node values, but the structure of the tree is wrong.

I don't have a copy of that book, so I can't see exactly what you're seeing, but maybe these notes will be helpful. At the end of section 11.15, the author talks about 2-parameter and 3-parameter versions of foldTree, and shows that only mapTree' written to use the 3-parameter version will work correctly.

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • I have a vague feeling it *can* be done by folding into an ad-hoc monoid ([e.g.](https://stackoverflow.com/questions/55662192/building-a-list-of-all-branches-in-a-tree/55672941#55672941)) to carry the structure along (and foldr *is* equivalent to foldMap), but I haven't thought this through. – Will Ness Aug 23 '20 at 19:03
  • Thanks, the notes are helpful. I'm new to catamorphism. Is there a way to test if my answer to the first question is correct without using mapTree? I used `foldTree (:) [] testTree` to test. Are there other ways? – maxloo Aug 24 '20 at 00:57
  • 1
    @WillNess, the issue here is that `foldMap f ac (Node (Node Leaf 1 Leaf) 2 Leaf)` and `foldMap f ac (Node Leaf 1 (Node Leaf 2 Leaf))` are both equal to `f 1 (f 2 ac)`, so those two trees can't be distinguished. – K. A. Buhr Aug 24 '20 at 15:54
  • 1
    @maxloo, a list is the "most general" output that `foldTree` can produce, so if `foldTree (:) []` works for all trees, then it will work for any other `foldTree f b`. So, that's probably the best test. – K. A. Buhr Aug 24 '20 at 15:56
  • 1
    thanks, it is clearer now. so, in fact, with `foldt :: (a -> r -> r) -> BinaryTree a -> r -> r` it is `foldt g Leaf = id ; foldt g (Node lt a rt) = foldt g lt . g a . foldt g rt`, so that `foldt g (Node (Node Leaf a Leaf) b Leaf) = (id . g a . id) . g b . id` vs. `foldt g (Node Leaf a (Node Leaf b Leaf)) = id . g a . (id . g b . id)`. and to have a hope for the two to be distinguishable, not only this `id` must not be a unit element wrt this `.`, this `.` must not be associative, too. – Will Ness Aug 25 '20 at 06:22
  • 1
    (I wrote that out in such detail for the benefit of others who might be reading in the future). @maxloo that `foldt` is equivalent to `foldTree` version you wrote in the question, just with a different argument order, so it's an element-oriented folding as in `Foldable`, not a structural catamorphism. We can recover the linear, structure-ignoring, accumulator-passing folds from the data type-following structural folds by [folding into endofunctions](https://stackoverflow.com/a/52452266/849891), but not the other way around. – Will Ness Aug 25 '20 at 06:32
  • @WillNess: `foldt :: (a -> b -> b) -> BinaryTree a -> b -> b` is the wrong type signature. I tried to test using `foldt (:) testTree` on the command line, but it threw an error: `Cannot find "show" function`. Is it because there's no accumulator in your solution? I must admit I haven't studied until `Foldable` yet.. – maxloo Aug 25 '20 at 11:13
  • 1
    @maxloo it's not wrong, it just has different order of arguments to your `(a -> b -> b) -> b -> BinaryTree a -> b`. (it does exactly the same thing as your function; if you substitute your temp values and re-write it with `flip` and `(.)`, you get `flip (foldTree f) left . f a . flip (foldTree f) right $ b`) . ---- try `foldt (:) testTree []` -- it expects a starting (or ending, however we choose to look at it) `b` value as an argument. for `(:)`, it's usually `[]`, but can be e.g. `[1337, 42]` or any other `[Integer]` list. – Will Ness Aug 25 '20 at 13:05
  • @WillNess: Thanks, `foldt (:) testTree []` works. For `flip` and `(.)`, do you mean I change my foldTree to this: `foldTree f b (Node left a right) = flip (foldTree f) left . f a . flip (foldTree f) right $ b` ? I get an error: `Non-exhaustive patterns in function foldTree` if I test it with `foldTree (:) [] testTree`. I also get an error if I test it with `foldTree (:) testTree []`. – maxloo Aug 25 '20 at 13:45
  • @WillNess: Sorry, I don't understand the uses of `flip` and `(.)` very well. I take it that flip switches the operands of a function, but here we have `flip (foldTree f) left .` where the last operand is a `(.)`. Does it switch `(.)` with `left`? – maxloo Aug 25 '20 at 14:16
  • 1
    it is a *re-write* of the second clause, not the whole definition. `flip` just flips the arguments, `flip f x y = f y x`; `.` lets us omit the explicit argument: `(f . g) x = f (g x)` i.e. `(f . g) = \x -> f (g x)`. no deep meaning behind `flip`, although working with `.` makes us focus on the functions themselves, not on temporary values which are the results of those functions' applications. so we can say `foldt g tree x` produces a new value, `x2`, for this `x`; or we could say `foldt g tree` is a function, which, given an `x`, will produce `x2`. – Will Ness Aug 25 '20 at 16:20