Let's say we have g :: a -> b
, and f :: b -> c
. We can write:
f . g :: a -> c
.If our functions return monadic values (i.e. values in contexts), for example, g1 :: (Monad m) => a -> m b
and f1 :: (Monad m) => b -> m c
. We can write:
f1 <=< g1 :: (Monad m) => a -> m c
.return x >>= g1 >>= f1
, where x :: a
, to get a value back. Or even the lambda \x -> return x >>= g1 >>= f1
.It seems that <=<
is more parallel to .
in terms of syntax. <=<
makes it easier to understand Monad
is just about function composition that preserves context. Why is >>=
more often talked about than <=<
?
<=<
is a great way of explaining the monad laws:
f <=< return = f -- right identity
return <=< g = g -- left identity
f <=< (g <=< h) = (f <=< g) <=< h -- associativity
And it's very useful for demonstrating the category of Kleisli arrows:
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Category (Kleisli m) where
Kleisli f . Kleisli g = Kleisli (f <=< g)
id = Kleisli return
And you'll see it show up in point-free programs. Personally, I'm also fond of its peer =<<
.
But while it makes talking about the monad laws and composition easier, I think there's still some strong didactic reasons that >>=
is preferred among monad tutorials and introductions to Haskell.
The first reason being that <=<
's strong suit is point-free code, and for the most part point-free code is harder for people coming from a language in the C-syntax family (C, C++, Java, Python, etc) to understand at first.
If "point-free" is an unfamiliar adjective to you, here's three implementations of the same function:
f a b = a + b * 2
f a = (a +) . (* 2)
f = flip (.) (*2) . (+)
They all run the same calculation, but the last is in what's called point-free style, where the variables on the left have been removed via eta conversion.
This example is very much a strawman, but point-free style is seductive and can easily lead to code that is very difficult for beginners to understand.
Another reason is that one of the near-universal questions beginners ask is
"how do I unwrap a IO String
to get an String
?" when first confronted with
Haskell's IO
monad. The answer of course is, "you don't, you chain the rest
of the computation with >>=
", >>=
making it easy to explain the relationship
between
putStrLn "Your first name: " >>= \_ ->
getLine >>= \first ->
putStrLn "Your last name: " >>= \_ ->
getLine >>= \last ->
putStrLn ("Hello " ++ first ++ " " ++ last)
and
do
putStrLn "Your first name: "
first <- getLine
putStrLn "Your last name: "
last <- getLine
putStrLn ("Hello " ++ first ++ " " ++ last)
One last reason, of course, is that >>=
is in the definition of Monad
, and
<=<
isn't, and that's just the way the language is defined to be. People are
more likely to talk about typeclass members than arbitrary functions when
teaching others about the typeclass, especially when the teacher is
relatively new to the subject themselves (as so many monad tutorial authors are).
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