I'm working through the wonderful Haskell Book. At the end of the Traversable chapter (21), I need to write an instance for the following Tree:
data Tree a =
Empty
| Leaf a
| Node (Tree a) a (Tree a)
Here is a link to the full code of my solution. The exercises recommends trying to implement both foldMap
and foldr
. This is how I implemented foldr
(without putting much thought into the invocation order):
foldr _ z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) =
f x $ foldr f (foldr f z left) right
I then implemented foldMap
as follows:
foldMap f Empty = mempty
foldMap f (Leaf x) = f x
foldMap f (Node left x right) =
foldMap f left <> f x <> foldMap f right
When I run QuickCheck's foldable
test batch, I get some failures. Changing my foldr
implementation to the following makes all the tests pass:
foldr _ z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) =
foldr f (f x (foldr f z right)) left
I tried running the failing test case on my own, but couldn't recreate the failure:
*Ch21_12_ExercisesTree Data.Monoid> tree = Node (Node (Leaf (-5)) 3 (Node (Leaf 3) 5 Empty)) (-2) Empty
*Ch21_12_ExercisesTree Data.Monoid> foldr (<>) (mempty :: Sum Int) t
Sum {getSum = 4}
*Ch21_12_ExercisesTree Data.Monoid> foldMap Sum t
Sum {getSum = 4}
I suspect there's something I'm not figuring out about the fold
ing function that QuickCheck is using.
Questions:
foldr
can be obtained from foldMap
by using the Endo
monoid, with the a -> b -> b
function turning a
values into b -> b
functions that can be (monoidally) composed. That being so, if your foldMap
is...
foldMap f Empty = mempty
foldMap f (Leaf x) = f x
foldMap f (Node left x right) =
foldMap f left <> f x <> foldMap f right
... the corresponding foldr
must be:
foldr f z Empty = id z -- mempty amounts to id
foldr f z (Leaf x) = (f x) z
foldr f z (Node left x right) =
((\e -> foldr f e left) . f x . (\e -> foldr f e right)) z -- (<>) amounts to (.)
If we tidy that up a little...
foldr f z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) =
foldr f (f x (foldr f z right)) left)
... we get the correct definition of foldr
as written in your question. As the difference between the implementations has to do with the order of composition, trying out a non-commutative monoid readily leads to a failing case, as you have found out.
On the QuickCheck subquestion, I defer to DDub's answer..
As you already deduced, the reason you're getting failures is because the two implementations are distinguishable, which you can observe by using a non-commutative monoid.
Getting the function used by quickcheck is a not so simple. See, for example, this question/answer about Show
ing functions generated by quickcheck for a little more info.
The way to get Show
able functions out of QuickCheck is to wrap the function in the Fun
type. That said, the code you're calling (found here) just uses functions directly, so they can never be shown. One option you could try is to create your own version of the foldable
function where you use the type Fun a b
in place of a -> b
and applyFun
as necessary to apply the functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With