Lhooq
Lhooq

Reputation: 4441

Simplify a repetitive pattern using monads

I'm actually not sure this is doable with monads or that it should be done with them and I'm looking for a solution rather than solve it with monads if monads are not the solution.

Let's say I have the following code (simplified but the idea is here):

module IM = Map.Make (Int)

let f k v m =
  if IM.mem k m then (m, true)
  else (IM.add k v m, false)

let a =
  let m = IM.empty in
  let m, res = f 0 "Zero" m in
  if res then
    let m, res = f 1 "One" m in
    if res then f 2 "Two" m else (m, res)
  else (m, res)

I'm repeating multiple times:

let value, boolean = function application to value and other values in 
  if boolean then another function application to the new value and other values (not the same as the first one)
  else the result

This means that these functions don't necessarily have the same type but they all return a a IM.t * bool (a being the same type for every function)

I wondered if it was possible to create an inline operator that would allow me to do it.

I tried something like:

module EqMonad = struct
  let ( let* ) (t, res) f2 = if res then f2 t else (t, false)
  let return t = t
end

But it'll obviously not work because f2 takes more than one argument but needs to receive t as its last argument.

I guess I could summarize my problem like this:

Is it possible to have monads wrapping variadic functions?

Upvotes: 3

Views: 92

Answers (1)

octachron
octachron

Reputation: 18902

Your solution works (with return corrected to be the monadic return) and is a restricted version of the error monad:

let return x = x, true
let a =
   let m = IM.empty in
   let* m = f 0 "Zero" m in
   let* m = f 1 "One" m in
   let* m = f 2 "Two" m in
   return m

However, since the control flow never depends on the value of m, this is a sign that it might simpler to use a regular function:


let rec add_first_fresh m l = match l with
  | [] -> None
  | (k,v):: q ->
    if IM.mem k m then add_first_fresh m q
    else Some (IM.add k v m)

let a = add_first_fresh IM.empty
    [0, "Zero";
     1, "One";
     2, "Two"
    ]

Upvotes: 6

Related Questions