# Draft coroutine factory return type Pros/cons of varieties of `next()`. ```typescript /** * A multi-step computation that may be * scheduled for a step by calling the * [Generator.next] method. * * Block lambdas that extend [GeneratorFn] specify * factories for [Generator]s. * * Terminology note: * Coroutines may be represented as Generators * where [Generator.next] starts or resumes * computation and the coroutine pauses itself * when done with the step. * We avoid the term "coroutine" because many * programmers associate it narrowly with * parallelism. */ interface Generator<PAUSE_T, BUBBLE extends Bubble> { /** * Starts/resumes the computation and runs until * it pauses or completes. * * If the computation was [done] prior to the * start of the call or it is done at the end, * returns [Null]. * This is the case for a [GeneratorFn] that * [returns][snippet/builtin/return]. * * If the computation pauses, returns the value * associated with the decision to pause. * For a [GeneratorFn], this is the value passed to * [snippet/builtin/pause]. */ public next(): PAUSE_T | Null | BUBBLE; /** * True if subsequent calls to [next] would do * no work and return [Null]. */ public done: Boolean; /** * Should be called once it is realized that * [next] is not going to be called again to * release resources needed by [next]. * * After `close()` exits, [done] will be true. */ public close(): Void; } ``` ## Variant 1: Null means no result / not-resumable. ```typescript public next(): PAUSE_T | Null | BUBBLE; ``` Pros: - Simple interface. No object allocation. Cons: - Either *PAUSE_T* needs to exclude null or there's an ambiguity about what `.next() == null` means. If the latter, code that uses it generically needs to check `.done` separately and know to do so. - *null* means "no result," not "don't expect any result after this" ## Variant 2: Option type. None means no result / not-resumable. ```typescript public next(): Optional<PAUSE_T> | BUBBLE; ``` Pros: - Unambiguous - Reuses std type that is a semantic match - Idiom `when (g.next()) { ... }` Cons: - If a target language has a result type especially for generators, like JavaScript's `{ next, done }` record, the backend needs to adapt. - May require more pause value boxing when PAUSE_T is a pass-by-copy/primitive by copy. ## Variant 3: Generator helper type. ```typescript public next(): GeneratorResult<PAUSE_T> | BUBBLE; ``` ```typescript sealed interface GeneratorResult<out PAUSE_T> { public done: Boolean; } class GeneratorDone extends GeneratorResult<Never> { public get done(): Boolean { true } } class NextResult<PAUSE_T> extends GeneratorResult<PAUSE_T> { public value: PAUSE_T; public get done(): Boolean { false } } ``` Pros: - Unambiguous - Idiom `when (g.next()) { ... }` - Compiles to nice code on most backends. Cons: - Duplicative of optionals in Temper code. Meh: - If a target language has an optional type but does not have native support for generators, we can still connect this type to optionals. ## Variant 4: Bubble if next() ends without pausing. ```typescript public next(): PAUSE_T | Bubble; ``` Pros: - No need for BUBBLE type parameter - Slightly easier translation of *Generator.next* on target languages whose native *Generator* type throws (see near bottom about Python's *StopIteration*) to indicate no-next value. Cons: - Mild ambiguity between next() failed and next() successfully concluded correctly that there are no more values. ## Example usage We need to check `done` in some fashion before using a value returned from next. Here's an example: ```typescript let myGenerator = makeAGenerator { () extends GeneratorFn;; pause(1); // Client needs to call `next()` before continuing. console.log("About to return"); }; let x = myGenerator.next(); if (!myGenerator.done) { // not done console.log(x); // -> 1 } let y = myGenerator.next(); if (!myGenerator.done) { // done console.log(y); } ``` That might enable loops of this form for null-returning: ```typescript // Variant 1, null-returning while (true) { let x = myGenerator.next() orelse do { console.log("I'm so sad."); myGenerator.close(); bubble(); }; if (myGenerator.done) { break; // Guaranteed null `x` but meh. } process(x); } myGenerator.close(); ``` Or for helper return type: ```typescript // Variant 3, using match while (true) { let next = myGenerator.next() orelse do { console.log("I'm so sad."); myGenerator.close(); bubble(); }; match (next) { is NextResult -> process(next.value); else -> break; } } myGenerator.close(); ``` Or for bubbling: ```typescript // Variant 4, bubbling while (true) { let x = myGenerator.next() orelse do { if (myGenerator.done) { // Don't know if I should be sad? break; } console.log("I'm so sad."); myGenerator.close(); bubble(); }; process(x); } myGenerator.close(); ``` ## Connecting to backend types: exceptions when no-next. Some target languages raise an exception from a generator when there is no next value. For example, Python does `raise StopIteration()`. ```python >>> def f(): ... yield 1 ... yield 2 ... yield 3 ... >>> i = f() >>> type(i) <class 'generator'> >>> i.__next__() 1 >>> i.__next__() 2 >>> i.__next__() 3 >>> i.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> ``` Java does have `NoSuchElementException` but that is usually only thrown when a client fails to check `.hasNext()` properly. How do we translate `myGenerator.next()` uses from Temper code using a generator that might have come from backend code? ### Variant 1, 2, 3: trap exception ```python= # Python support code def temper_generator_next(generator): try: return generator.__next__() except StopIteration: # For variant 1 return None # Temper null # Or for variant 2 return EmptyOption # Or for variant 3 return DoneResult # Then we can translate `foo.next()` to temper_generator_next(foo) ``` ### Variant 4 Make sure we treat *StopIteration* as a bubble.