<div dir="ltr">async functions only address the async use case and they're all scheduled on a simple micro-task queue. We need fine grained control over scheduling. Perhaps Zones can help a bit with that but that's just one of severals concepts that need this.<div><br></div><div>It doesn't solve other more general generator use cases. You could potentially expand it to generators as well.</div><div><br></div><div>However, then you'd also need to solve the nested handlers case efficiently. That's my use case. What if you have a layout handler, in an iteration handler in an async scheduler handler?</div><div><br></div><div>The async functions could be implemented in terms of this though since they're just a more specific and locked down version.</div><div><br></div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Mar 15, 2016 at 5:16 PM, Ben Newman <span dir="ltr"><<a href="mailto:ben@benjamn.com" target="_blank">ben@benjamn.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">What if we simply allowed await expressions anywhere in the call stack of an async function, rather than only in the bodies of async functions? That would give us all the power of "yielding deep in a fiber" with a much more familiar syntax, and an easy way to capture effects, since an async function returns a Promise that allows for asynchronous error handling. No new catch effect … syntax needed.<div><br></div><div>I've talked about this <a href="http://benjamn.github.io/goto2015-talk/#/17" target="_blank">before</a>, and it's my understanding that TC39 needs a lot of convincing that coroutines are a good idea. We haven't really discussed the topic in the context of async functions, which are soon to be an official part of the language, so perhaps the time for discussing coroutines/continuations/etc. is drawing near!</div></div><br><div class="gmail_quote"><div><div class="h5"><div dir="ltr">On Tue, Mar 15, 2016 at 7:44 PM Sebastian Markbåge <<a href="mailto:sebastian@calyptus.eu" target="_blank">sebastian@calyptus.eu</a>> wrote:<br></div></div></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="h5"><div dir="ltr"><div>Has anyone previously proposed a Algebraic Effects (e.g. Eff like handlers of continuations) for ECMAScript? #lazyweb  I could only find variations that aren't quite this.</div><div><br></div><div>I'm specifically looking at the OCaml implementation for inspiration:</div><div><br></div><div><a href="http://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/" target="_blank">http://kcsrk.info/ocaml/multicore/2015/05/20/effects-multicore/</a></div><div><br></div><div><a href="http://kcsrk.info/slides/multicore_fb16.pdf" target="_blank">http://kcsrk.info/slides/multicore_fb16.pdf</a></div><div><br></div><div>I'm not proposing the multicore aspect of this but just the delimited continuations.</div><div><br></div><div>Basically, the idea is that you can capture effects by wrapping a call in a "try". That spawns a fiber. Then any function can "perform" an effect as a new language feature. That works effectively like "throw" expect it also captures a reified continuation, in a tuple. The continuation can be invoked to continue where you left off.</div><div><br></div><div>Imaginary syntax:</div><div><br></div><div>function otherFunction() {</div><div>  console.log(1);</div><div>  let a = perform { x: 1, y: 2 };</div><div>  console.log(a);</div><div>  return a;</div><div>}</div><div><br></div><div>do try {</div><div>  let b = otherFunction();</div><div>  b + 1;</div><div>} catch effect -> [{ x, y }, continuation] {</div><div>  console.log(2);</div><div>  let c = continuation(x + y);</div><div>  console.log(c);</div><div>}</div><div><br></div><div>Prints:</div><div>1</div><div>2</div><div>3</div><div>4</div><div><br></div><div>(`perform` is a contextual keyword to "throw" and `catch effect` is a keyword for catching it.)</div><div><br></div><div>We've experimented with changing React's implementation to use these internally to handle concurrency and being able to solve complex algorithms that require a lot of back and forth such as layout calculation. It seems to make these implementations much easier while remaining efficient.</div><div><br></div><div>It also allows for seamless async I/O handling by yielding deep in a fiber.</div><div><br></div><div>Effectively this is just solving the same thing as generator and async/await.</div><div><br></div><div>However, the benefit is that you don't have a split between "async" functions, generator functions and synchronous functions. You still have an explicit entry point through the place where you catch effects.</div><div><br></div><div>With generators and async functions, anytime you want to change any deep effects you have to unwind all potential callers. Any intermediate library have to be turned into async functions. The refactoring is painful and leaves you with a lot of syntax overhead.</div><div><br></div><div>If you want to nest different effects such as layout, iterations and async functions that complexity explodes because now every intermediate function has to be able to handle all those concepts.</div><div><br></div><div>The performance characteristics demonstrated by KC Sivaramakrishnan are also much more promising than JS VMs has been able to do with async/await and generators so far. It's plausible that VMs can optimize this in similar way, in time. I suspect that the leakiness of the microtask queue might cause problems though.</div><div><br></div><div>I converted the OCaml example scheduler to this ECMAScript compatible syntax:</div><div><br></div><div>// RoundRobinScheduler.js</div><div><br></div><div>class Fork {</div><div>  constructor(fn) {</div><div>    this.fn = fn;</div><div>  }</div><div>}</div><div>function fork(f) {</div><div>  perform new Fork(f)</div><div>}</div><div><br></div><div>class Yield { }</div><div>function yieldToScheduler() {</div><div>  perform new Yield();</div><div>}</div><div><br></div><div>export function run(main) {</div><div>  const run_q = [];</div><div>  function enqueue(k) {</div><div>    run_q.push(k);</div><div>  }</div><div>  function dequeue() {</div><div>    if (run_q.length) {</div><div>      run_q.shift()();</div><div>    }</div><div>  }</div><div>  function spawn(f) {</div><div>    try {</div><div>      f();</div><div>      dequeue();</div><div>    } catch (e) {</div><div>      console.log(e.toString());</div><div>    } catch effect Yield -> [_, k] {</div><div>      enqueue(k);</div><div>      dequeue();</div><div>    } catch effect Fork -> [fork, k] {</div><div>      enqueue(k);</div><div>      spawn(fork.fn);</div><div>    }</div><div>  }</div><div>  spawn(main);</div><div>}</div><div><br></div><div>// Example.js</div><div><br></div><div>import * as Sched from "RoundRobinScheduler";</div><div><br></div><div>function f(id, depth) {</div><div>  console.log("Starting number %i", id);</div><div>  if (depth > 0) {</div><div>    console.log("Forking number %i", id * 2 + 1);</div><div>    Sched.fork(() => f(id * 2 + 1, depth - 1));</div><div>    console.log("Forking number %i", id * 2 + 2);</div><div>    Sched.fork(() => f(id * 2 + 2, depth - 1));</div><div>  } else {</div><div>    console.log("Yielding in number %i", id);</div><div>    Sched.yield();</div><div>    console.log("Resumed number %i", id);</div><div>  }</div><div>  console.log("Finishing number %i", id);</div><div>}</div><div><br></div><div>Sched.run(() => f(0, 2));</div><div><br></div></div></div></div>
_______________________________________________<br>
es-discuss mailing list<br>
<a href="mailto:es-discuss@mozilla.org" target="_blank">es-discuss@mozilla.org</a><br>
<a href="https://mail.mozilla.org/listinfo/es-discuss" rel="noreferrer" target="_blank">https://mail.mozilla.org/listinfo/es-discuss</a><br>
</blockquote></div>
</blockquote></div><br></div>