I'm looking to learn F#, but one thing that's confusing to me is the computation expression (do-notation??) syntax and desugaring.
In haskell, you have a very simple Monad typeclass and rules for desugaring do-notation into bind and return. There's no magic involved in adding keywords; the only thing must match up are the types.
In F# there's a bunch of builders, and keywords, and complexity.
Is there a good explanation of how to map one concept to the other?
I basically want to know how I map
do
x <- monadicComputation
foo x
someOtherMonadicComputation
let y = somePureComputation x
return $ bar y
to F#.
The only keywords in the haskell are do, (<-) and let.
An expression evaluates to a result (usually written (e rightsquigarrow r) but we'll use e -- > r ). Haskell uses a similar notation for numbers and operators as most languages: 2 -- > 2. 3+4 -- > 7. 3+4*5 {equivalent to 3+(4*5)} -- > 23.
To create a monad, it is not enough just to declare a Haskell instance of the Monad class with the correct type signatures. To be a proper monad, the return and >>= functions must work together according to three laws: (return x) >>= f ==== f x.
What is a Monad? A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.
F#+ generic computation expressions provide a convenient syntax for writing monadic expressions.
You can't write generic monadic code in F#, instead you have to specify the monad you're working in by naming the builder associated with the expression. Your example code would look like:
let example = async {
let! a = someAsyncComputation
foo a
do! someOtherAsyncComputation
let y = somePureComputation a
return (bar y)
}
for the async
computation expression type. The 'bang' pattern (do!, let! etc.) is used when binding monadic values, while the regular keywords are used for non-monadic values.
let!
corresponds to bind (>>=)
while let
corresponds to let
in do
notation. return
corresponds to return
, while return!
is used to yield an existing monadic value. do!
is similar to (>>)
which executes a monadic value for its effects, while do
is for non-monadic effects, which has no parallel in Haskell.
If you come from the Haskell background than you might be interested in an academic article that I wrote about F# computation expressions recently.
It links the computation expression syntax (which is quite flexible) to standard type classes that are used in Haskell. As already mentioned, F# does not easily let you write code generic over monad (it can be done, but it is not idiomatic), but on the other hand it lets you choose the most appropriate syntax and you can even get nice syntax for MonadPlus
or for monad transformers.
In addition to the async
monad that was mentioned by Lee, here is an example of MonadPlus
(using the sequence expression - a list monad - as an example):
let duplicate list = seq {
for n in list do
yield n
yield n ∗ 10 }
Or a computation expression for parsers:
let rec zeroOrMore p = parse {
return! oneOrMore p
return [] }
The haskell do notation has only one special syntax i.e <-
which is mapped to bind
function, everything else inside do is just normal function application for which the result is the monad type, for example: return
, putStr
etc.
Similarly in F# you have let!
to represent bind
operation and return
keyword syntactic sugar (not normal function call like in haskell but this keyword maps to the Return
function that you define). Now there are many other keywords that your computation expressions can support (you can omit them easily if not required), they all are documented here. These additional operations sort of give you the syntactic sugar to use F# keywords instead of normal functions that return the monadic value. You can see that all the keyword that you can overload in F# computation expressions have monadic return value.
So basically, you don't need to be worried about all these keywords, just think of them as normal monad returning functions (with a specific type signature that you can find in documentation) that you can call using F# keywords inside the computation expression syntax.
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