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.