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

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.

Well that’s been a long-time OCaml philosophy, right? To be very explicit about everything. It’s only recently that we’re seeing some implicit behaviours start to come in to the language.

2 Likes

Thanks to everyone who replied. Just for fun here is one more way, which is probably overkill

let orelse x y = match x with Some _ -> x | None -> Lazy.force y;;
let ( /* ) x f = f x and ( */ ) f x = f x;;

let _ = Some 1 /*orelse*/ lazy (Some 2);;
let _ = None /*orelse*/ lazy (Some 2);;

I must confess that the /* something */ trick confused when I first saw it.

2 Likes

Ha, I hope no one is actually doing this :joy:

3 Likes

One says to oneself idly “gee, I see an International Obfuscated Ocaml Contest” in our future!

1 Like

Most people would call that an ‘OCaml Contest’ :joy:

1 Like

I wrote two of these functions in my app; <||> is my lazy version, e.g. Some(5) <||> lazy (Some (6+3)), and <|> for cases that can use a strict second argument. I decided I didn’t want to overload boolean ||.

I also added <??> for the “or default” variant of this logic, e.g. Some(5) <??> lazy (6+3)), and <?!> which takes a strict value. I chose this name because I’m teaching ReasonML to JS programmers, and it’s modelled on the JavaScript nullish coalescing operator (??). Unfortunately, while I can define ??, the associativity difference renders it fairly pointless in use.

One thing to keep in mind is that <||> is left-associative, so if you have a long chain of them, a lazy thunk will be created for each before evaluating the option-expressions left-to-right. That has the intended semantics, but might allocate more than you expect. The let|| version, on the other hand, should create one closure for each option-expression that evaluates to None. Presuming that the compiler doesn’t inline / hoist / etc. those closures away, which would be even better.

1 Like

Ah, yeah, good point. So far we’ve only used it for small expressions, not long chains, but I will note that in the docs. Thanks!

I actually am a fan of monads, which is maybe why this approach bothers me?

I don’t deny that the short-circuit thing is useful, but using the bind operator (or even worse, overriding let||) is problematic, since this usecase has nothing to do with the notion of binding.

The implementation is clever, but using >>= or let|| in this way is somehow akin to

module Wat = struct
  let ( + ) a b = a * b
end

i.e. these symbols have established meanings, and this usecase breaks those expectations. If such a behavior were added to the standard library (or any other library), I think it would make a lot more sense to use another operator.

this discussion reminds me of something I did recently. When I experiment a program, I like to print lots of debug information. Because I’m lazy I would usually do something like this:
let pr s = if !debug then print_endline s else ()
and then print debug info like this:
sprintf "Here is the result = %f." x |> pr

Of course I know all these sprintf are evaluated even if !debug is false. So finally I decided that the computer should be Lazy, not I, and I tried:

let lazy_pr s = if !debug then print_endline (Lazy.force s) else ()
...
lazy (sprintf "Here is the result = %f." x) |> lazy_pr

Result: of course the syntax is less appealing, but since I was using many of them in large loops, the lazy version increased the speed of my program by about 4% or 5%.
(with !debug=false, of course)
This makes me think…

Let* seems to have become the commonly accepted alias for bind, and let+ for map. let|| seems to be entirely up for grabs. (I agree that having >>= as other than the bind operator is poor practice.)

2 Likes