Search code examples
haskellmonadsfoldfunction-composition

Folding, function composition, monads, and laziness, oh my?


I am puzzled. I can write this:

import Control.Monad

main = print $ head $ (foldr (.) id [f, g]) [3]
  where f = (1:)
        g = undefined

and the output is 1. That makes sense, because it reduces to:

main = print $ head $ ((1:) . undefined . id) [3]
main = print $ head $ (1:) ((undefined . id) [3])
main = print $ head $ 1 : ((undefined . id) [3])
main = print $ 1

But if I use a vaguely similar monadic technique, it doesn't work the same:

import Control.Monad

main = print $ (foldr (<=<) return [f, g]) 3
  where f = const Nothing
        g = undefined

This hits prelude.Undefined. Which is odd, because I would expect it to reduce:

main = print $ ((const Nothing) <=< undefined <=< return) 3
main = print $ return 3 >>= undefined >>= (\_ -> Nothing)
main = print $ Nothing -- nope! instead, undefined makes this blow up

However, flipping the order of composition:

import Control.Monad

main = print $ (foldr (>=>) return [f, g]) 3
  where f = const Nothing
        g = undefined

does accomplish the expected short-circuiting and produces Nothing.

main = print $ (const Nothing >=> undefined >=> return) 3
main = print $ (const Nothing 3) >>= undefined >>= return
main = print $ Nothing >>= undefined >>= return
main = print $ Nothing

I suppose comparing the two approaches might have been comparing apples and oranges, but can you explain the difference? I thought that f <=< g was the monadic analogue to f . g, but they are apparently not as analogous as I thought. Can you explain why?


Solution

  • It depends on which monad you're working with, and how its (>>=) operator is defined.

    In the case of Maybe, (>>=) is strict in its first argument, as Daniel Fischer explained.

    Here are some results for a handful of other monads.

    > :set -XNoMonomorphismRestriction
    > let foo = (const (return 42) <=< undefined <=< return) 3
    > :t foo
    foo :: (Num t, Monad m) => m t
    

    Identity: Lazy.

    > Control.Monad.Identity.runIdentity foo
    42
    

    IO: Strict.

    > foo :: IO Integer
    *** Exception: Prelude.undefined
    

    Reader: Lazy.

    > Control.Monad.Reader.runReader foo "bar"
    42
    

    Writer: Has both a lazy and a strict variant.

    > Control.Monad.Writer.runWriter foo
    (42,())
    > Control.Monad.Writer.Strict.runWriter foo
    *** Exception: Prelude.undefined
    

    State: Has also both a strict and a lazy version.

    > Control.Monad.State.runState foo "bar"
    (42,"*** Exception: Prelude.undefined
    > Control.Monad.State.Strict.runState foo "bar"
    *** Exception: Prelude.undefined
    

    Cont: Strict.

    > Control.Monad.Cont.runCont foo id
    *** Exception: Prelude.undefined