see discussions in #1214
after some research, below is the syntax I think will be great and extensible in the future
let [@bs] v0 = promise0 in (* default to Promise.bind *)
let [@bs] v1 = promise1 in
let [@bs Option] v2 = optionl2 in (* now default to Option.bind *)
let [@bs ] v3 = optional3 in
let [@bs Promise] v4 = promise4 in (* back to promise *)
let [@bs error] v5 = error_handling in (* here bind to Promise.catch *)
...
We chose attributes instead of extension (like janestreet let%bind) because attributes allow payload for more customization later
todo:
research on semantics of let .. and
Bob, can we do something like what dune is doing and backport the new let* syntax? From that post:
The shim preprocessor converts bindings operators to OCaml identifiers of the form let__XXX and and__XXX. For instance, let+* is translated to let__plus_star. So you must make sure to not use such identifiers in your code.
I imagine this would be something like let$plus$star in BuckleScript output JS? We could have this shim in bsppx and get rid of it once BuckleScript lands 4.08+.
OCaml 4.08.0 is out. Would be really cool to have what @yawaramin suggested now!
There are some nice features in 4.08, so we may target 4.08.1 sooner than original timeline, we will see in next few months
@bobzhang, in order to be able to actually use let*/let+ for async binding, what are all the things that would have to happen? I was trying to dig into it a while back and this is what I understood from asking around:
4.08.1.let*/let+.let into a callback.Is that roughly correct? I apologize if my terminology is all wrong. I'm not very experienced in language development. But since you folks are busy making with other cool language features, I thought maybe I could help organize thoughts or efforts around this feature, and take some pressure off of you.
I think this bind sugar really matters to the growth of the community, especially those Javascript developers who are used to async/await and thinking of coming to Reason. I personally have found that I end up reaching for https://github.com/jaredly/let-anything in pretty much every new project that I start. It can drastically improve the readability of my code.
According to @anmonteiro this is what needs to happen before we can have a feature like this included in the language:
- refmt would need to upgrade to the 4.08 AST (it's currently on the 4.04 AST - I have a PR upgrading it to the 4.06 AST but it's more than a year old...)
- BuckleScript would need to upgrade to the 4.08 compiler if people using BuckleScript would like to take advantage of this syntax.
Is that in line with your thoughts @bobzhang?
On the native side, Dune has future_syntax (https://dune.readthedocs.io/en/stable/dune-files.html#future-syntax) to allow older compilers to use let+. That preprocessor could also be adapted. But it's probably "smarter" to switch to 4.08.
Has there been any progress on this front?
@nadav-lavy-exa a lot! See https://github.com/facebook/reason/pull/2487
Is this now available as part of bs-platform?
Wanted to add my $0.02 on this, after the repackaging of ppx_let for rescript, working with bs-let for many months, and recently also using Reason's let operators through reason-repacked.
Right now, the following Reason code which uses a let extension:
let fn = () => {
let%bind p = Js.Promise.resolve(42);
let%bind x = Js.Promise.resolve(p * 2);
Js.Promise.resolve(x + 42);
};
Is converted to:
let fn = () =>
%bind(
{
let p = Js.Promise.resolve(42)
%bind(
{
let x = Js.Promise.resolve(p * 2)
Js.Promise.resolve(x + 42)
}
)
}
)
The original suggestion is to make it look like this:
let fn = () => {
let [@bs] p = Js.Promise.resolve(42)
let [@bs] x = Js.Promise.resolve(p * 2)
Js.Promise.resolve(x + 42)
}
Or, if I use a module that provides an abstraction over promises for long running async operations:
let fn = () => {
let [@bs AsyncOp] p = AsyncOp.execQuery(42)
// then option 1
let [@bs AsyncOp] x = AsyncOp.return(p * 2)
AsyncOp.return(p * 2)
// Or option 2
let [@bs AsyncOp.map] x = p * 2
(x + 42)
}
My thoughts:
bs-let, ppx_let) will work out-of-the-box.let%bind) is concise and meaningful with less typing than an attribute, esp. when the attribute requires _at least_ 4 characters (3 keystrokes) to begin with. With attributes, using a non-default module will require 4 keystrokes + the module name. When combined with modules like in ppx_let, you get a very robust system.An example of the last point:
module AsyncOpSpace = {
let bind = AsyncOp.bind
let map = AsyncOp.map
let return = AsyncOp.return
}
let fn = () => {
open AsyncOpSpace // this provides context
let.bind p = AsyncOp.execQuery(42)
let.map x = p * 2
(x + 42)
}
Most helpful comment
There are some nice features in 4.08, so we may target 4.08.1 sooner than original timeline, we will see in next few months