First post here, so forgive me if this question has an obvious answer!
I was looking at the Js.Option and the Belt.Option in the API documentation and was wondering what are the differences between the two. The Js.Option seems to be “data-last” in the sense that the option you pass in comes last, as compared to Belt.Option “data-first” approach.
The data first seems nicer to use when chaining together operations eg:
// This is a bit cleaner...
Some(thing)->flatMap(doSomething)->flatMap(doSomethingElse)
// Than this
Some(thing)->andThen(doSomething, _)->andThen(doSomethingElse, _)
Other than data-first arguably looking a bit nicer, are there any other differences that are important (practically speaking?)
Example
Here is a little example with the compiled JS for illustration:
let reciprocal = (. x) => x == 0. ? None : Some(1. /. x)
let jsVersion = Js.Option.andThen(reciprocal, Some(2.))
let beltVersion = Belt.Option.flatMapU(Some(2.), reciprocal)
Here is the compiled JS output:
function reciprocal(x) {
if (x === 0) {
return ;
} else {
return 1 / x;
}
}
var jsVersion = Js_option.andThen(reciprocal, 2);
var beltVersion = Belt_Option.flatMapU(2, reciprocal);
So the JS functions are different, but will it really make a difference which to use? Should one be preferred over the other?
TL;DR
Are there any important differences between Js.Option.andThen and Belt.Option.flatMapU (uncurried version) other than the order of their arguments?
This is a good question and stems from a greater question that still trips me up.
The answer is: they are exactly the same. The implementations of the two methods in the standard library are:
function flatMapU(opt, f) {
if (opt !== undefined) {
return f(Caml_option.valFromOption(opt));
}
}
and
function andThen(f, x) {
if (x !== undefined) {
return f(Caml_option.valFromOption(x));
}
}
The bigger question is why are there two and how do you choose?
I believe Js.Option is an older syntax intended to work with the older Reason and OCAML ecosystems, which favour pipe-last (|>) syntax. Belt.Option is more suitable to the Rescript ecosystem which favours pipe-first (->). There are a few libraries in the Js module that support both syntaxes (e.g: Array and Array2), but there is no Js.Option2, at least, not yet.
Belt and Js have a lot of overlap. I generally choose Belt whenever possible, but if I am working with objects that are going to be sent into a Javascript binding, I’ll sometimes switch to the JS module.
Thanks for the reply. Guess I should get mode comfortable looking through the ReScript compiler source code!
I had a question about this:
I generally choose Belt whenever possible, but if I am working with objects that are going to be sent into a Javascript binding, I’ll sometimes switch to the JS module.
That’s interesting that you mention choosing Belt when possible. The API docs seem to suggest picking Js if possible:
If you can find your API in this module, prefer this over an equivalent Belt helper. For example, prefer Js.Array2 over Belt.Array
Do you think the prefer Js suggestion is for an older version of rescript maybe?
Well, it’s a matter of zero-cost vs. safety. So Belt methods are usually safer and mostly immutable, while the Js module exposes only what’s already available in JavaScript anyway. If you write a library, you may not want to bloat your bundle with Belt.