5

The below code fails the checkers test for traversable. I'd appreciate an explanation of why it fails, not just how to fix it.

import Test.QuickCheck
import Test.QuickCheck.Checkers
import Test.QuickCheck.Classes

data List a =
  Nil
  | Cons a (List a)
    deriving (Show, Eq, Ord)

instance Functor List where
  fmap _ Nil = Nil
  fmap f (Cons x xs) = (Cons (f x) (fmap f xs))

instance Foldable List where
  foldr f z (Cons x xs) = f x z
  foldr _ z Nil = z

instance Traversable List where
  traverse f Nil = pure Nil
  -- traverse f (Cons x Nil) = Cons <$> f x <*> pure Nil
  traverse f (Cons x xs) = Cons <$> f x <*> (traverse f xs)

instance Arbitrary a => Arbitrary (List a) where
  arbitrary = sized go
    where go 0 = pure Nil
          go n = do
            xs <- go (n - 1)
            x <- arbitrary
            return (Cons x xs)

type TI = List

instance Eq a => EqProp (List a) where (=-=) = eq

main = do
  let trigger = undefined :: TI (Int, Int, [Int])
  -- quickBatch (functor trigger)
  quickBatch (traversable trigger)

Here you see that it passes the fmap laws, but not the foldMap ones:

λ> main

traversable:
  fmap:    +++ OK, passed 500 tests.
  foldMap: *** Failed! Falsifiable (after 6 tests): 
<function>
Cons 4 (Cons (-2) (Cons (-5) (Cons 5 (Cons 2 Nil))))
The Unfun Cat
  • 29,987
  • 31
  • 114
  • 156

1 Answers1

6
instance Foldable List where
  foldr f z (Cons x xs) = f x z
  foldr _ z Nil = z

Your Foldable instance doesn't traverse the tail of the list.


checkers tests your Traversable instances by testing Functor and Foldable all together: it derives foldMap and fmap from your implementation of Traversable and make sure they produce the same result as foldMap and fmap you defined.

zakyggaps
  • 3,070
  • 2
  • 15
  • 25
  • But how do I write the foldable instance? It expects a `b` not a `List b` so I cannot write something like `foldr f z (Cons x xs) = Cons (f x z) (foldr f z xs)` – The Unfun Cat Mar 21 '16 at 11:20
  • @TheUnfunCat By `foldr f z xs` you got a `b`, combined with `x` now you have two values of type `a` and `b`, if there is some function produces a new `b` from them...? – zakyggaps Mar 21 '16 at 11:26
  • Const is the only one I can think of... There might be some in the class for Applicative or whatevz but I am stumped. – The Unfun Cat Mar 21 '16 at 11:34
  • @TheUnfunCat try look at the type signature of foldr again: `(a -> b -> b) -> b -> t a -> b` – zakyggaps Mar 21 '16 at 11:36
  • 3
    Ok, got it: `foldr f z (Cons x xs) = f x (foldr f z xs)` but do not understand why it worked. Will just follow bitemyapps advice and `Don't sweat the stuff you don't understand immediately. Keep moving!` – The Unfun Cat Mar 21 '16 at 11:43
  • @TheUnfunCat it worked because it is. `f :: a -> b -> b` means `f (x::a) (y::b) :: b`. I liked your trick using `Nil` in Traversable, you should try it with Foldable too: `foldr g z Nil = z`; `foldr g z (Cons x Nil) = z` (just for the type) or better `= g x z == g x (foldr g z Nil)`. Next substitute `xs` for `Nil` (their types are the same)... – Will Ness Mar 21 '16 at 13:12