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

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

I see what you mean, but the established used of let* and let+ are both related to binding a value to a name within a certain scope.

I don’t mean only binding in the monadic sense, but really binding in any sense. i.e. what let has always meant. Using let|| () = ... as syntactic sugar for a thunk is weird, since it is normally used to prove that lhs expression is equal to unit. Visually, this code is nonsense.