Laurel Language Designer Guide

10.1. Where to extend🔗

Broadly, the design choices are made on four axes:

  • Communication model — how tasks exchange information: shared state versus message passing. To support both, Laurel pairs coroutines with shared memory: coroutines may interleave their executions while reading and writing shared memory. Additionally, yield/resume may carry payloads, playing the role of message channels between asynchronous procedures.

  • Scheduling model — who decides when a task runs: cooperative versus preemptive. Laurel chooses cooperative, since this model matches the semantics of a wide range of concurrent programs from upstream languages. The counterpart, preemptive concurrency, is commonly seen in operating systems, where an interrupt can suspend a process's execution; it can be modeled by instrumenting cooperative coroutines with a yield inserted between atomic commands.

  • Language feature — the surface construct. By choosing the cooperative concurrency model, Laurel mirrors the surface syntax of many upstream languages. For instance, in Python a coroutine suspends via yield and resumes via next(...); in JavaScript a coroutine suspends via yield and resumes via coro.next(...), where coro is a suspended coroutine. Laurel's yield/resume constructs act as the yield/next of these languages.

  • Runtime model — how a suspended task's state is represented: stackful versus stackless. Laurel chooses stackless coroutines, since as of now none of the upstream languages Laurel targets have stackful coroutines. Moreover, stackful coroutines can be encoded with stackless ones by propagating suspensions outward, so if Laurel later extends support to languages with such semantics, the basic building block is ready.