How do you feel about an Option.merge function in Stdlib?

Here is what I mean. I have the following piece of code

  let theme = match theme with Some _ as x -> x | _ -> preferences#get_theme

And I would like to replace it with this:

  let theme = Option.merge theme preferences#get_theme

What should merge do is obvious. It returns the second argument if the first is None

This is similar to ‘null coalescing’ or the ‘alternative’ (‘or else’) operation. I would probably define it like

let (or) option1 option2 = match option1 with
  | Some _ -> option1
  | None -> option2

Usage:

let theme = theme or preferences#get_theme

or is already a keyword in OCaml, even if its usage is discouraged.

coalesce can be an option

My main point was to try to make it an infix operation. Option.(theme || preferences#get_theme) is also not a bad idea, imho.

1 Like

Option.merge leads to the question, “How does it merge them?”. Why does it prefer the first one over the second one?

If you decide to do this, consider naming it Option.first_some like Base does since that makes the semantics obvious.

2 Likes

No monad-acolyte I, but this does seem like something monadic … maybe there’s something over in those parts that looks like this?

The only difference between this function and one that simply unwraps an option safely (ie. with a ~default argument) is that here the default argument isn’t evaluated right away but is instead a function to be called if needed. This is why Containers calls this function get_lazy (corresponding to its get_or, which preceded the stdlib’s value but has the same functionality). A similar name for this function that matches the stdlib’s terminology would be value_lazy.

2 Likes

Want something like this? with monadic syntax support, it might be nice ?

module OM = struct let (>>=) v f = match v with Some _ -> v | None -> f()  end ;;
module OM :
  sig val ( >>= ) : 'a option -> (unit -> 'a option) -> 'a option end
# OM.( (Some 2) >>= (fun () -> Some 1)) ;;
- : int option = Some 2
# OM.( None >>= (fun () -> Some 1)) ;;
- : int option = Some 1

Yes. Overriding ( || ) in Option will allow one to write

  let v = Option.( opt_a || opt_b || opt_c )

My question was mostly whether this would be an useful addition to Stdlib. Since other libraries have it the answer seems to be Yes.

1 Like

The good thing about the monadic way of doing this [and I’m certainly not a fan of monads] is that you get the short-circuit behaviour of conditional-or. if you’re going to put something into stdlib, it seems like that’s important, no?

It’s not to me to put something in Stdlib. Even if I can open an issue/PR

Not a fan of monadic style either, but evaluation by need in this case is certainly a plus. I’ll have a look at what can be done wit let+

I do not see ~default argument being evaluated lazily.

let value o ~default = match o with Some v -> v | None -> default

I can do it with Option.fold but it seems overkill

Thanks @Chet_Murthy for suggesting the Monad Way. I’ve tried the let+ and it works but I will not call it an improvement.

Judge for yourself

let ( let// ) v f = match v with Some _ -> v | None -> f ();;

let// x = Some 1 in Some 3 (* x is Some 1 *);;
let// x = None in Some 3   (* x is Some 3 *);;

let f () = print_endline "********"; Some 9;;

let// x = Some 1 in f () (* x is Some 1 *);;
let// x = None in f ()   (* x is Some 9 and some stars are printed *);;

I’ll probably stick with pattern matching for now.

But now I’d like to have something like that in Stdlib

module Option
...
let bind o f = match o with None -> None | Some v -> f v
..
  module Syntax = struct
    let ( let+ ) = bind
  end
end

There is an open PR to add let-operators to the OCaml Stdlib for the various monadic types.

Oh, now here’s another way to do this, that … seems syntactically more … “pleasant”?

module OM = struct let (>>=) v f = match v with Some _ -> v | None -> Lazy.force f  end ;;

So you use it thus:

# OM.( (Some 1) >>= lazy (Some 2)) ;;
- : int option = Some 1
# OM.( None >>= lazy (Some 2)) ;;
- : int option = Some 2

Not arguin’ for it – just noting that it seems … pleasant.

I wonder whether it’s efficient compared to the method with closures.

Someone on here some time ago came up with a let|| (let-or) operator which I noted down at the time because it seemed quite interesting. It will accept the first option which does not evaluate to None. You can make the default the last value:

let (let||) opt f =
  match opt with
  | Some _ -> opt
  | None -> f ()

So you could have this:

# let x =
  let|| () = Some 10 in
  let|| () = Some 20 in
  Some 30;;
- :      val x : int option = Some 10
let x =
  let|| () = None in
  let|| () = None in
  Some 30;;
- :      val x : int option = Some 30
2 Likes

That was an interesting thread: Using let-binding syntax to implement option 'or'

1 Like

The fact that the (let||) operator automatically puts everything after the first binding into a thunk, and so on thereafter, is the nice part I think, because it gives you proper short-circuiting. I think that outweighs the slight complication of the syntax.

1 Like

Yeah, it also lends itself nicely to resource handling: Downsides to calling Gc.full_major at exit?

I’m feeling this sort of … disquiet, that there’s no way to get this same functionality using infix operators. Let is fine for sure, but it seems unfortunate that there isn’t some way to have an infix operator that implicitly lazy-i-fies its right-hand-side argument, so that we can express short-circuit-ing without using let.