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.
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.
Ha, I hope no one is actually doing this
One says to oneself idly “gee, I see an International Obfuscated Ocaml Contest” in our future!
Most people would call that an ‘OCaml Contest’
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.
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.)
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.