All we’ve looked at so far is Functor behaviour — we haven’t looked at laws for any of the monad operations.

Some natural transformations are monad morphisms, which satisfy a couple more laws: one is that return works the same before and after the morphism; the other is that bind works the same before and after the morphism.

Let’s QuickCheck those for maybeToList and listToMaybe.

return commutes with either of these transformations:

> quickCheck (\(x :: Integer) ->

listToMaybe (return x) == return x)

+++ OK, passed 100 tests.

> quickCheck (\(x :: Integer) ->

maybeToList (return x) == return x)

+++ OK, passed 100 tests.

It turns out, though, that although the bind vs morphism law does hold for maybeToList , it does not for listToMaybe :

checkMorph morph b = quickCheck $ \a ->

(morph (do v <- a ; b v))

== (do v <- morph a ; morph (b v))

Let’s make actions that “filter out” values less than 5:

filterMaybe :: Integer -> Maybe Integer

filterMaybe v = if v >= 5 then Just v else Nothing filterList :: Integer -> [Integer]

filterList v = if v >= 5 then [v] else []

so we can QuickCheck:

> checkMorph maybeToList filterMaybe

+++ OK, passed 100 tests.

But…

> checkMorph listToMaybe filterList

*** Failed! Falsifiable (after 7 tests and 4 shrinks):

[0,5]

QuickCheck has found an exception. Let’s work through it.

One way, we can run the computation in [] and convert to Maybe at the end:

[0,5] >>= filterList -- = [5]

listToMaybe [5] -- = Just 5

Or we can convert to Maybe at each step and bind those:

listToMaybe [0,5] -- = Just 0

filterList 0 -- []

listToMaybe (filterList 0) -- Nothing

So if we stay in the [] monad as much as possible, we can explore all possible paths for answers, giving a list of answers, and do a “safe head” on that list at the end.

If we go to Maybe as soon as possible, we discard everything except the first choice as soon as possible — we only ever tried filterList on 0 , rather than on both 0 and 5 . Maybe is not rich enough to express all the things we can in [].

Going the other way, anything using Maybe only ever has at most one path to explore (and possibly none) — and [] is rich enough to express that.

So in that sense, Maybe is “smaller” than [], and that’s why I put “mini-lists” in the post title.