reason icon indicating copy to clipboard operation
reason copied to clipboard

Built in syntax for async.

Open jordwalke opened this issue 9 years ago • 14 comments
trafficstars

I've really liked Lwt's syntax for async (including the integration with try, let, and let + and for simultaneously async calls). We can bake this into Reason by default with something like let!, and then allow configuration of which library it should represent.

Imagine putting an attribute at the top of the file such as:

[@async Lwt];

or

[@async Async];

which could influence which library is used for that first class let binding syntax.

For the implementation, we could reuse Lwt's ppx plugin perhaps.

jordwalke avatar Mar 14 '16 07:03 jordwalke

Syntax for monadic code will interact well with implicits: if you desugar

 let! x = e in
 let! y = e' in
   return (g x y)

into

 e >>= fun x ->
 fun e' >>= fun y ->
 return (g x y)

then you can just use implicits to pick the appropriate implementation of >>=.

Since the desugaring should be the same in any case, even without implicits you can write something like this

  Lwt.(
   let! x = e in
   let! y = e' in
   return (g x y)
  )

to avoid the need for a magic attribute.

yallop avatar Mar 14 '16 07:03 yallop

What would be the default? Optionals? That would be useful.

cristianoc avatar Mar 14 '16 08:03 cristianoc

F# computation expressions are nice. You can specify the “builder” outside the let! block so you can have let! with different meaning in one file depending on the context.

gaearon avatar Mar 14 '16 12:03 gaearon

Yes, I also think that the F# async ability to select the desired monad on a per-block rather than per-file basis is a significant benefit. Nesting them (essentially shadowing the outer let! by the inner one) also ought to work.

jberdine avatar Mar 14 '16 17:03 jberdine

@gaearon That sounds like what I'm describing with attributes. You can put the "provider" of that asynchronicity at the to of the file, or even scoped to some lexical block, because you can place attributes anywhere in the tree.

jordwalke avatar Mar 14 '16 21:03 jordwalke

Ah okay, I didn’t realize

you can place attributes anywhere in the tree

gaearon avatar Mar 14 '16 21:03 gaearon

Ah okay, I didn’t realize

It's not obvious, and that doesn't mean it won't require some effort to replicate the nice scoping rules of lexical scoping.

We might be able to do it without attribute nodes in the AST, and instead just open the right module.

jordwalke avatar Mar 14 '16 22:03 jordwalke

@yallop Have you seen Lwt's ppx extension? It seems to do that and much more (considers and bindings as being parallel, also unifies match/with with this same monadic concept). I am very much looking forward to modular implicits as well.

jordwalke avatar Mar 15 '16 07:03 jordwalke

@jordwalke: I'm not that familiar with Lwt's ppx extension, but I know Jane Street's ppx_let, which does something similar (including parallel computation support for let ... and) and various other implementations. I think the important thing is to have two independent components:

  1. a way to desugar things like let into things like >>=. There are a few choices here (Lwt's ppx extension, ppx_let, ...)
  2. a way to choose which >>= to use in a particular block of code. This should be a separate step from desugaring and is best done via core language mechanisms such as local opens or implicits.

yallop avatar Mar 15 '16 12:03 yallop

I'm probably missing some context here, but if ! means what I think it means, how would you express the following (which is valid using Babel/regenerator in JavaScript today) in Reason:

async function(): Promise<void> {
  if (await someCall()) {
    const [first, second] = Promise.all([thisReturnsAPromise(), thisAlsoReturnsAPromise()]);
  }
}

I believe there was a proposal to add syntactic sugar for Promise.all() that died, which would have allowed for something like:

async function(): Promise<void> {
  if (await someCall()) {
    const [first, second] = await* thisReturnsAPromise(), thisAlsoReturnsAPromise();
  }
}

bolinfest avatar May 18 '16 18:05 bolinfest

See also #1321

jaredly avatar Jun 14 '18 15:06 jaredly

i think that with my proposal, await can be an ordinary suspendable function. async can be an ordinary function that wraps a lambda in a promise. No need to create more syntax, since internal DSLs assembles language itself using functions. https://github.com/facebook/reason/issues/1982

let myAsyncFunc = async {
  body
};

await @@ myAsyncFunc();

wiltonlazary avatar Jun 14 '18 20:06 wiltonlazary

No need to reinvent the wheel, some languages already have the battle tested solution to this.

https://github.com/facebook/reason/issues/1982 internal DSLs + coroutines "aka suspendable functions".

wiltonlazary avatar Jun 14 '18 20:06 wiltonlazary

This can probably be closed now that Reason supports OCaml's monadic/applicative let syntax, yes?

mlms13 avatar Apr 03 '20 18:04 mlms13