Is Haskell's bind operator (>>=) equivalent to F#'s forward pipe operator (|>)?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Is Haskell's bind operator (>>=) equivalent to F#'s forward pipe operator (|>)?



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





No. |> is like $. >>= is like *.collect, but generalised.
– AJFarmar
Aug 12 at 14:00


|>


$


>>=


*.collect





Potential close voters: this is definitely not opinion-based. It is possible to answer it objectively by considering the involved types and the differences between the languages (cf. the existing answers).
– duplode
Aug 12 at 17:27




2 Answers
2



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.


m


IO


(>>=) @IO


|>



If we specialize m to Maybe, then >>= is like Option.bind, just with the arguments flipped (which makes sense, since >>= is pronounced “bind”).


m


Maybe


>>=


Option.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.


m


Either e


>>=


Maybe


Left


Nothing


|>



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:


m


Parser


megaparsec


>>=


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).


|>


c



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:


m


(->) r


>>=


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.


>>=


IO


IO


IO


>>=


>>=


Monad


IO



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#:


|>


&


Data.Function


(&) :: a -> (a -> b) -> b



This function is quite useful in its own right, but it has nothing to do with monads.





1) The Key thing doesn’t work out: a) We need f :: Key -> String and b) Key should be the second argument to the other two (or (<*>) should be used). 2) (>>=) (really (=<<)) is a form of application. There’s a tower of power: ($)/(&) for pure functions; (<$>) for Functor; (<*>)/(<**>) for Applicative; (=<<)/(>>=) for Monad.
– HTNW
Aug 12 at 17:53


Key


f :: Key -> String


Key


(<*>)


(>>=)


(=<<)


($)


(&)


(<$>)


Functor


(<*>)


(<**>)


Applicative


(=<<)


(>>=)


Monad





@HTNW For your first point: yes, you’re right, I have no idea what I was thinking. I’ve updated the example to be what I imagine I originally had in mind. As for your second point, I agree, but I think it’s outside the scope of this answer.
– Alexis King
Aug 12 at 18:00



While F# doesn't distinguish between pure and impure operations, it does have the notion of monads. This is most observable when you work with computation expressions. In order to implement a computation expression, you must implement monadic bind. In the F# documentation, this must have the type M<'T> * ('T -> M<'U>) -> M<'U>, although this is pseudo-code, since a type like M<'T> isn't proper F# syntax.


M<'T> * ('T -> M<'U>) -> M<'U>


M<'T>



F# comes with some built-in monads, such as Async<'a>, 'a list, 'a seq. You can also trivially create computation expression for 'a option and Result, although I don't think any of those are built in.


Async<'a>


'a list


'a seq


'a option


Result



You can peruse the source code for the various computation expression builders to identify how monadic bind is implemented for each, but AJFarmar is right that they're often called collect:


AJFarmar


collect


> List.collect;;
val it : (('a -> 'b list) -> 'a list -> 'b list)

> Array.collect;;
val it : (('a -> 'b ) -> 'a -> 'b )

> Seq.collect;;
val it : (('a -> #seq<'c>) -> seq<'a> -> seq<'c>)



Not always, though. Sometimes the operation is called bind:


bind


> Option.bind;;
val it : (('a -> 'b option) -> 'a option -> 'b option)



To illustrate, consider this little F# helper function to parse a string into an integer:


open System

let tryParse s =
match Int32.TryParse s with
| true, i -> Some i
| _ -> None



You can use the forward pipe if you have a string:


> "42" |> tryParse;;
val it : int option = Some 42



On the other hand, if your string is already is in an option value, you'd have to use monadic bind:


option


> Some "42" |> Option.bind tryParse;;
val it : int option = Some 42



The |> operator also exists in Haskell, but you have to import Data.Function:


|>


Data.Function


Prelude Data.Function> :t (&)
(&) :: a -> (a -> b) -> b






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard