Type signature of Haskell's bind operator (>>=):
m a -> (a -> m b) -> m b
Type signature of F#'s forward pipe operator (|>):
'a -> ('a -> 'b) -> 'b
They look similar.
And considering the impure nature of F#,
the equivalent operator of |>
in Haskell is >>=
?
For example:
Haskell:
getLine >>= putStrLn
F#:
stdin.ReadLine() |> stdout.Write
Not really. If you specialize m
to IO
, then there are some superficial similarities, so maybe it’s true that (>>=) @IO
is sort of like F#’s |>
, but in general, the similarity does not hold.
If we specialize m
to Maybe
, then >>=
is like Option.bind
, just with the arguments flipped (which makes sense, since >>=
is pronounced “bind”).
ghci> Just [1, 2, 3] >>= headMay
Just 1
ghci> Just [] >>= headMay
Nothing
ghci> Nothing >>= headMay
Nothing
If we specialize m
to Either e
, then >>=
does something similar to what it does for Maybe
, short-circuiting on Left
values instead of Nothing
. These examples are sort of similar to using |>
with functions that raise exceptions, but they aren’t quite the same.
If we specialize m
to Parser
(from, say, the megaparsec
package), then >>=
produces a new parser that runs a first parser, then uses its result to determine which parser to run next. For example, this defines a parser that produces a parser that parses two digits or a non-digit followed by an arbitrary character:
p :: Parser Char
p = anyChar >>= \c -> if isDigit c then digit else anyChar
This is rather different from |>
, since we’re not running anything, merely building up a structure (a parser) that will be applied to a value later, yet the code still talks about the value that will eventually be provided (in the c
binding).
If we specialize m
to (->) r
, then >>=
implements a kind of implicit argument passing. For example, if we have a set of functions that all accept a common argument:
f :: Key -> String
g :: String -> Key -> Char
h :: Char -> Key -> Bool
…then we can use >>=
to compose them together, passing the same first argument to all of them:
ghci> :t f >>= g >>= h
f >>= g >>= h :: Key -> Bool
This is clearly different from |>
, since we’re performing a sort of function composition, not function application.
I could go on, but listing dozens of examples probably isn’t any more helpful than just listing a few. The takeaway is that >>=
is not just for sequencing effectful things, it is a much more general abstraction of which sequencing IO
actions is a special case. The IO
case is pragmatically useful, of course, but it’s also probably the least theoretically interesting, since it’s a little bit magical (IO
is baked into the runtime). These other uses of >>=
are not magical in the slightest; they are defined entirely using ordinary, pure Haskell code, but they’re still very useful, so they’re much more relevant to understanding the essence of >>=
and Monad
than IO
is.
As a final aside, Haskell does have a function just like F#’s |>
. It is called &
, and it comes from the Data.Function
module. It has the same type that it does in F#:
(&) :: a -> (a -> b) -> b
This function is quite useful in its own right, but it has nothing to do with monads.
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