Monadic programming tutorial

I have typed a little monadic programming tutorial.

https://www.sinerj.org/~loyer/monadic_programming/monadic_programming/index.html

Hope this helps. Your comments are welcomed.

3 Likes

I have added an option monad section. option monad is less powerful than the list monad (an option is like a list of zero or one item), but this monad can be very handy with functions like Base.Hashtbl.find or Stdlib.Hashtbl.find_opt which returns an option we don’t wan’t to match with Some x explicitly.

Nicely done.

Do you have anything to say about other monads? I’m thinking about state, reader, writer…

The state monad looks particularly interesting to me but I’ve only toyed with it thus far.

Hi,

Thanks for the idea. I don’t think these monads are implemented in Base/Core. However, implementing them should not be difficult. I could propose a section “implementing its own monad”.

About the State monad, I am puzzled about its usefulness in Ocaml since it is made to deal with the absence of mutable field (ref contents for example). And if my memory is ok, dealing with two states is rather cumbersome.

Regarding Lwt, instead of starting with let%lwt and then explaining that it behaves the same as let* in the previous sections, I would suggest starting by defining let* (Lwt.bind), and then later explaining that Lwt has a preprocessor that provides the let%lwt construct, which is just let* with better behavior with respect to exception backtraces. In particular, none of your Lwt examples actually require on the preprocessor, so you could move the whole explanation to an appendix, thus avoiding polluting your explanation about monadic programing.

3 Likes

Although depending on what you are doing, the limitation to only 1 item at most can be an important feature. Like you can see the option monad as a list monad with an added powerful invariant that the item count never exceeds 1.

So I wouldn’t say option is “less powerful” than list.

1 Like

I have included a section about the state monad. This monad is interesting since the different monads get, put x are not just plain values but functions which get the state as an argument even if we don’t pass explicitly this argument (it is the job of run or bind).

I have applied other proposals.

It can be useful, for instance when you combine it with a continuation monad (which gives what I call a choice monad, but I just found it is also called a select monad).

This is a thing that can be needed when implementing a symbolic interpreter (and even more when you want the interpreter to be able to do both concrete or symbolic execution and thus is a functor parameterized by this monad). The one linked is doing multi-core exploration so it is a bit more complex.

1 Like

I’m not sure if that’s what you meant, but I’ve learned lately that Haskell does have mutable variables. So I’m not sure that the use of this technique is motivated by the absence of mutable fields.

I’ve only scratched the surface (via the preface library), but I like the idea of describing the mutation of state as a pure function. I suspect it could help design better programs as well as making testing easier. Plus, overall, I like the constraint a monad enforces.

Suppose I’ve got a random number generator.

val random : int -> int

I can now describe a pure function that produces a random int.

let rand_int : int Random.t =
  let* seed = Random.get in
  let new_seed = random seed in
  let* () = Random.set new_seed in
  Random.return new_seed
;;

I can derive another pure function from it

let rand_bool : bool Random.t =
  Random.map
    (fun n -> n mod 2 = 0)
    rand_int
;;

Or even more complicated structures:

let rand_tup3 : (int * bool * int) Random.t =
  let* a = rand_int in
  let* b = rand_bool in
  let* c = rand_int in
  Random.return (a, b, c)
;;

Those functions are purely descriptive, which I find quite fascinating.

The mutable equivalent would be:

let gen_int =
  let seed = ref 0 in
  fun () ->
    seed := random !seed;
    !seed
;;

let gen_bool () =
  (gen_int ()) mod 2 = 0


let rand_tup3 : int * bool * int =
  let a = gen_int () in
  let b = gen_bool () in
  let c = gen_int () in
  (a, b, c)
;;

Which is roughly the same amount of code, but with less meaning and guaranties.

I haven’t fully connected the dots though, I would gladly learn more.

Thanks for your input @zapashcanon , and thanks @Frederic_Loyer for the article update.

Thanks for the article. We note that even with the mutable variable, a monad sequence is involved (do ... r <- readIORef 0 ...)

I don’t know when it has been introduced. I have looked at the Haskell language more than 10 years ago and didn’t remember of it. Using state monad is ok when there is only one variable to deal with… Hugs does’nt support it.

Trivial nit: doesn’t work very well on my android mobile.

What I would like to see is some kind of “motivation” section. What problems do monads solve? What can I do with a monad that I cannot do otherwise? Are they merely a convenience or is something more substantial involved?

A difficult question… Haskell has introduced it by necessity since no side effect where allowed and modads were introduced to emulate side effects. On the previous quoted article, Control.Data.IORef and Data.STRef are only accessible in a monad context. OCaml has not such constraints.

However, the option section proposes a way of sequencing functions which may return Some thing, or return None. Monads may render things quite easy.

With Lwt, monads are used to modelise a sequence of IO and schedule them (a bit anachronic when Ocaml 5.0.0 and Eio is out… but there are still Lwt code to deal with)

It can be interesting to enumerate other interesting pro.

So, I once spent a lot of time trying to understand the monad concept (I even read Moggi’s papers, or at least ran my eyes over them) without really grokking the stuff. But what I took away was that it boils down to sequencing or chaining stuff that does not “naturally” support that. Ie turning non-monoids into monoids, something like that. Am I close?

For example Io monads do not eliminate side effects, they just make them manageable by enforcing sequentiality.

Which leads to a related question: in what circumstances does it make sense to use monads when side effects are not an issue?

Yes, it enforces sequentiality since the bind operator (>>=) get the return value of the first argument and pass it as an argument to the second which is a function.

However, we never have a sequence issue in OCaml : a; b is always executed in the right order.

I guess the main added value from a OCaml point of vue is when the bind and run functions are doing some interesting scheduling things. In my page, there are 2 such examples: option and Lwt,

stm: Software Transactional Memory - FP Complete gives and interesting other examples where a monad schedule something multiple times. But I am not sure it applies to OCaml (Kcas doesn’t seem to follow a monadic pattern).

I am trying to propose a new design of the state monad.

Something simple, base on a type

type 't state_monad = 
  Result of 't | Get | Put of 't | Bind of ('t -> ... state_monad)

However, I can’t figure out the type it: if I instanciate the ...in the Bind constructor, I had to add parametric types in the front of monad… then they are needed again inside Bind… I can’t write this definition as I want.

I have upgraded the introduction with the interest of monads (mainly with lwt… its use is mandatory with some application server, and error handling where it can be convenient - see Caqti/Rapper tutorial- , and add a light section about the result monad.

I have look at Monads · OCaml Documentation lately… and I find many differences of approach between mine version and the ocaml.org version.

I guess a mix of the two versions would be interesting. The ocaml.org version is more precise in the introduction… and my version goes further with some convenient notations : let*, let%lwt (lwt_ppx), let%bind (ppx_let)… (even Haskell has a corresponding notation (do a <- b...;b <- ... ) why not introduce them ?).

The approches are quite different: ocaml.org has a more pedagogical approach (it creates from scratch a Maybe/Option monad) and my tutorial is more “practical” (let’s use the Option monad from Jane Street), even if a “custom monad” section is proposed.

Since the added value of my tutorial is mainly about the reuse of available notations, a second page about “Monads complements: convenient notation” could be adequate (no need to patch the Monads page which is tagged C3110 which bring some attribution issue). However, the operators page does introduce (let*) and bind without a link to the monad page.

If you haven’t seen it, I enthusiastically recommend Std (monads.Monads.Std) for an intro to the concept for working programmers. Imo, it is a really nice way of helping develop the intuition for monadic constructs as a way of parameterizing computational contexts.

Let’s not. For a tutorial on ocaml.org, let’s instead use the Option monad from the standard library. Or better still, use the Result monad from the standard library with the let* operator, and present it as an alternative to the use of exceptions. The Result monad with the let* operator in my view presents a compelling first introduction for beginners to the use of monads in OCaml.

5 Likes

Interesting, really ! The introduction goes from the pseudocode a := b; expr, then introduces the bind and its corresponding >>=. I guess it quite nice in a pedagic way.

But I will go further and introduced the let a = b in expr which:

  • goes back to the original idea,
  • is imported by a open Monads.Std.Monad.Option or something like this

My understanding is that Monads are made to simplify the programming, then the bind function is important to understant what happens under the hood, but the let* is important to present the program in a elegant way.

The transformer parts is interesting… but lack some example and seems a bit too abstract. The Lwt/Result combo use case could be interesting (I don’t know if a lift could have solved any issue. I note that a Lwt_result monad is introduced to solve our problems).