-
Notifications
You must be signed in to change notification settings - Fork 430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Syntax proposal: async/await #1321
Comments
Sounds good! Perhaps defining something like
At the top of the file could be how you select the async "provider". I really like Lwt's syntactic ppx plugin, and we could provide first class syntax for it. |
Hmmm actually I'm thinking that all I really need is |
does this not require a decision on what the standard for async should be ? |
Actually, I've got a ppx together that isn't tied to any specific async implementation, as long as it adheres to a general signature. |
@jaredly - have you thought more about this since July? I'm pretty interested in async/await syntax in reason -- the current async tools are a detractor for people coming from the JS side if you're used to having async/await. It would certainly make it an easier sell to re-write some of Expo's existing JS with Reason. |
@brentvatne Syntactic sugar for callbacks would not full featured, and has some pitfalls - however, it is much lighter weight by default, and is simply sugar for supplying a final callback argument: "Lightest Weight Threads" has a nice ring to it: Any function that accepts a callback of some regular form would be compatible. You could imagine the
More examples, including how https://gist.github.com/jordwalke/1cd1f18ef060e2a5226f54f31417f6f2 We could instead build syntactic support for integrating with fully featured promise libraries such as |
I liked the Backcalls of LiveScript. |
@jordwalke - the "Lightest Weight Threads" api looks elegant and easy to use. Coming from the JS side, my main concern is that when integrating Reason code with existing JS libs we'd want to be able to use this syntax with functions that return Promises, ideally without having to manually wrap them with another function for interop. Suppose |
@brentvatne Yeah, we'd want to create a way to interop with full featured promise libraries (including JS promises). You could specify a promise provider perhaps: let myFunction = () => {
open Promise;
let! thisIsTheResultOfJSPromise = asyncCall();
thisIsTheResultOfJSPromise + 10;
}; Or for short: let myFunction = () => {
Promise.let thisIsTheResultOfJSPromise = asyncCall();
thisIsTheResultOfJSPromise + 10;
}; By default, you might consider that there's this hypothetical
|
I really like the idea of A |
For any syntax it would be nice to support the same for other keywords like Lwt.switch promise {
| A => ...
| B => ...
| exception Not_found => ...
} |
why not just use the same api as in js ?
This would require a standardized promise implementation. |
I propose adding two syntax constructs which could be described as syntax sugar
SyntaxLet-do-bindingsReason:
OCaml:
OCaml (after preprocessing with ppx_let):
Let-do-bindings are used when you need to exract a value form a monad and define Do-expressionsReason:
OCaml:
OCaml (after preprocessing with ppx_let):
Do-expressions are used when you need to perform a series of side-effectful Using syntax with concrete monadsppx_let desugars I like how we can use local module open to bring the needed
Parallel bindingslet_ppx supports "parallel"-bindings:
This maps on let-do-bindings:
The downside (?) is that Note that ppx_let implements parallel bindings in terms of Adding an implicit return value for the last do-expressionCurrently in a chain of do-expression, the last value must be some monadic
We can add such return value implicitly in terms of the current ppx_let doesn't have this feature so I'd describe it mapping from Reason to Note the refined definition of Reason:
OCaml:
Async-specific syntax vs. Monadic syntaxSimply, I believe async-specific keywords don't bring much value given that The advantage is that we can use the proposed syntax with other monads than
The async computations use case already requires us to provide different
ppx_let approach solves that elegantly with |
Good discussion. Here's some thoughts/questions:
|
The only case, I can think of, which is useful to introduce an implicit
In that case instead of parsing it as:
we parse it as:
which is then transformed into:
To opt-out — simply add a desired expression after the
Now the question is how to refer to the current
will be transformed into:
Similar thing for The complete example would look like:
The downside is that
Maybe instead we should ask to put (btw. In my prev comment I forgot to wrap the last expression of
I think
Now you can say that such "genericness" is not a good thing. But now I think we should've been arguing whether we need a generic monadic syntax or async-centered syntax. Anyway the separate keyword
I think it would be easy to explain The syntax with local module open already looks very similar to async/await functions in JS:
If we can somehow make local module open syntax "less noisy", then it gets even better:
|
What about discussion in rescript-lang/rescript-compiler#1326? 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 *)
... |
If I could throw my 2c in, as someone relatively new to the language, I dislike the use of I really like @andreypopp 's "do" proposal however. |
I like @andreypopp 's "do" proposal too. Getting local module open to be less noisy shouldn't be a problem. |
Note: |
For something like the module Promise = {
module Let_syntax = {
let bind = (~f, promise) =>
Js.Promise.then_(f, promise);
module Open_in_body = {
let return = (v) =>
Js.Promise.resolve(v)
}
};
};
let getJSON = (url) => {
/* Some syntax to say that we want to use Promise for any "do" syntax
without opening the module */
do with Promise;
/* Or some ppx-like [%do Promise]; */
let resp = do fetch(url);
let data = do resp.json();
return(Yojson.Safe.parse(data));
}; |
Maybe we can solve that by educating people not to have unnecessary values inside modules which provide module Promise = {
module Do = {
module Let_syntax = {
let bind = (~f, promise) =>
Js.Promise.then_(f, promise);
}
};
};
let someOtherBinding = ...
};
let getJSON = (url) => Promise.Do({
let resp = do fetch(url);
let data = do resp.json();
return(Yojson.Safe.parse(data));
}); That means we won't need to introduce another syntax construct. Also it leaves power users with the ability to define some utilities which can be used along with |
We could minimize the syntax impact by using something closer to existing syntax like |
@andreypopp @let-def What would you imagine we do for constructs like If your proposal draws attention to the similarity between async/await, then I would imagine you'd have the following special cases for do M;
bind (M, ~f:(() => E))
let P = do M;
bind (M, ~f:(P => E))
switch (do M) {
| P1 => E1
};
bind (M, ~f:(fun P1 => E1));
if (do M) {E1} else {E2};
bind(M, ~f:(fun true => E1 | false => E2)) The thing I like about putting the keyword on the right hand side is that it's then very clear exactly which value is being "awaited on".
|
Implicit |
For the record, here's the blog post I wrote on this subject recently: https://jaredforsyth.com/2017/12/30/building-async-await-in-reason/ (and accompanying ppx https://github.com/jaredly/reason_async) (I'm also rather against a |
@jaredly What are your thoughts on async/await (in JS?) |
I'm now enjoying Reason with |
I like |
take a look on, https://github.com/wiltonlazary/cps_ppx |
@wiltonlazary I think if you documented it with extended README for simple API usage that would help |
I think that js only people dont know well the concept of coroutines that async computation can be built around |
I think it's actually quite well known in the JS world, for example |
ReasonML is going on direction of monadic computation for async, i dont think that folks here are very inclined to coroutines or the https://fsharpforfunandprofit.com/series/computation-expressions.html |
@wiltonlazary informative link. The discussion is a bit disconnected but it does seem to be leaning toward generalized CPS. I personally admire F#. Being inspired by Haskell
I started by saying the discussion is leaning towards generalized CPS. Both PS neat PPX. I think the syntax could be less verbose. A better forum for feedback would be reasonml.chat. |
Computation expressions would be great as they are incredibly clean in F#. Also, try/catch in such implementation becomes much easier to reason about. |
Try to not reinvent the wheel, my life is much easy now thanks to the fantastic implementation of coroutine concepts as a flexible framework by https://kotlinlang.org/docs/reference/coroutines.html |
I have no clue if I am actually contributing, but I found this recent paper with the title Try/Catch and Async/Await are just a specialized form of Algebraic Effects! on Reddit and thought it might be relevant. |
Yep, that's a pretty standard example of Algebraic Effects actually (as are faking mutable variables in an entirely immutable world). OCaml has a HUGE side-developed system that includes one of the best Algebraic Effects systems I've seen short of having unbounded continuations and I hope to see it mainlined someday, but it seems slightly stalled (it would be SO useful though, especially combined with the multi-core changes). But Reason would not be able to emulate Algebraic Effects at this time, however the OCaml work on it would no doubt mind another programmer working on it, thus meaning Reason would then just transparently support it as well. :-) |
How will the recent merge of monadic and applicative let bindings in Ocaml affect this proposal? Can we just follow their lead? |
The sanest solution would be introducing 'noawait' and turning each function which uses an async function into an async function automatically. Example:
rational: If you start turning your functions into async .. its easy to miss something (eg also when merging code). This all important cases would be caught by the compile. The only problem would be if a function accepts any and any means Promises and values.. |
Given that As for BuckleScript, @bobzhang has an open issue for syntax sugar there rescript-lang/rescript-compiler#1326 |
@yawaramin but would that mean having a real reasonml syntax for async/await that is truly isomorphic? ie. same syntax for both bucklescript and ocaml? |
@benjamine I think we need to talk about them separately:
Once the above two issues are resolved, then yes, we would have the official OCaml |
@yawaramin oh, that makes sense, thanks for the detailed explanation 👍 |
Just tried an example from Let+ syntax backported to OCaml >= 4.02 post with OCaml 4.08.1 and Reason 3.5.0 @ 8ee1ff6, got not-so-user-friendly "Unknown error":
What's the latest status for let+ OCaml feature support? PR 2462 has promising title "OCaml 4.08 support" and is merged on Jul 4, Reason 3.5 @ 8ee1ff6 was released on Jul 15, so the PR should be included, but let+ syntax is not part of that PR? |
OCaml syntax and reason syntax are separate so changes to OCaml syntax won’t be propagated automatically to Reason syntax. I’d still want to have a similar feature in Reason as I find let-operators very useful in OCaml. |
I understand that it won't automagically appear in Reason :) Just trying to understand the roadmap regarding this feature. Probably it's of low priority, as BuckleScript does not support that, and thus primary community of Reason won't receive any immediate benefit out of it. Makes sense. Ppx_let mitigates this for native folks, just the less ppx you use, the better :) |
This is something I honestly expected to arrive in Reason before OCaml, I remember discussions that made me think Reason
Here's a contrived example: let do_things = user_id => {
/* Using let in inner blocks won't block, but instead return a new promise.
This maps more cleanly to Promise.then in my opinion. I'm not sure if this
is possible with JS's `await`, plus you don't need to mark functions as
`async`. */
let some_operation = {
let* () = do_something(user_id);
let* () = do_some_other_thing(user_id);
return("done");
};
let* user = get_user(user_id);
/* There are run in parallel, like Promise.all: */
let* () =
if (user.active) {
update_last_access(user_id);
} else {
return();
}
and* friends =
switch (user.friends) {
| None | Some([]) => return([])
| Some(ids) =>
let* friends = get_users(ids);
let* () =
List.map(friends, ~f=user => user.id)
|> Lwt_list.iter_p(update_last_access);
return(friends);
};
let* result = some_operation;
return(result == "done");
};
What is the proposal that the team is favoring at the moment? Putting OCaml's implementation aside, I would love to have something like https://github.com/jaredly/reason-macros as part of Reason. It can do anything Some example macros for ReasonReact & BuckleScript: [@macro.name "some.node"]
let%macro.let _ =
(pattern, value, continuation) =>
switch (eval__value) {
| None => React.null
| Some(eval__pattern) => eval__continuation
};
let%macro.let guard = (pattern, value, continuation) =>
switch (eval__value) {
| false => React.null
| true => eval__continuation
};
[@react.component]
let make = (~user) => {
let%guard () = user.city == "Amsterdam";
let avatar = {
let%some.node url = user.avatar;
<Avatar url />;
};
<div> avatar <span> {React.string(user.name)} </span> </div>;
}; let%macro nodeEnv = (value: string) =>
[%eval env("NODE_ENV")] == eval__value;
let endpoint =
if ([%nodeEnv "production"]) {
"https://www.example.com/graphql";
} else {
"http://0.0.0.0:8080/graphql";
}; |
I guess I'm late for this game. I made myself a PPX to resemble Sorry for the spam, I'm sharing with the hope that it may be useful to someone. |
With #2487 merged, this can probably be closed. |
Before this issue gets closed, I think it would be good to have a reference example for anyone who lands on this issue from a Google search in the future. We should show a side-by-side example of |
Perhaps it’s time for a blog post on the reason site as this issue definitely merits it and a link to that from here? |
let ops solve this |
Except using
lwt
instead of promises? orr we could not tie it to anything in particular and have it construct calls toasync_map fn (val)
so you could dolet async_map = Lwt.map
at the top of your file, orlet async_map fn val = Js.Promise.then_ (fun v => Js.Promise.resolve (fn v)) val
.Or maybe it should transform to
bind
instead ofmap
?The text was updated successfully, but these errors were encountered: