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?
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.
Identity: Lazy.
IO: Strict.
Reader: Lazy.
Writer: Has both a lazy and a strict variant.
State: Has also both a strict and a lazy version.
Cont: Strict.