Skip to content
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

Computation expressions should support syntax desugaring for return!/yield! in tailcall positions #1006

Open
dsyme opened this issue May 14, 2021 · 3 comments

Comments

@dsyme
Copy link
Collaborator

dsyme commented May 14, 2021

Computation expressions like task, seq, taskSeq, async, coroutine often need to support tailcalls in the computational structure, e.g.

let rec f n = 
    seq { 
        ...do something... ; 
        if n > 0 then
            yield! f (n-1) 
    }

Here tailcalling means that, once the first sequence is done its work, it delegates to the next sequence, and that chains of sequences either "cut out" or otherwise relinquish resources.

Tailcall detection of yield! is built in to the F# compiler for its compilation of sequence expressions (see this). However for other computation expressions this is harder. The problem is that return! and yield! are often used in non-tailcall positions, e.g. inside a try/with or try/finally, or sequencing of yield!. This makes it awkward to implement tailcalling semantics, especially for computation expressions that can be compiled with resumable code. Workarounds do exist but they increase the amount of code generated and have other problems.

Instead, we should simply augment computation expression de-sugaring to desugar return!/yield! to ReturnFromFinal/YieldFromFinal (or some other name) when they occur at the natural tailcall position if the method is present on the computation expression builder. Likewise do! in the final position can translate to ReturnFromFinal if present.

@TysonMN
Copy link

TysonMN commented May 16, 2021

This seems somewhat related to #721 to me.

@Lanayx
Copy link

Lanayx commented Feb 17, 2022

Tasks tailcall support is much appreciated!

@AngelMunoz
Copy link

I'll come to add my two cents use case here :)

I'm writing an async html rendering library which can produce a single string via StringBuilder or the main use is to render the html in chunks via IAsyncEnumerable<string> so they can be streamed as is to the client from a server, this can be used to leverage browser's incremental rendering.

This could also be used in the future to provide other client side benefits such as out of order rendering and many many things.

I initially went for for a recursive approach (since HTML lives as a tree data structure) with IcedTasks' ValueTask support + TaskSeq, however I had to change my approach to a stack based approach instead with limited recursion, when depth is more than ~200ish nodes we'd rather await to convert the whole IAsyncEnumerable<Node> sequence to a list then process it otherwise we have crashes at runtime due stack overflows, this limits how often can chunks be emitted from our side.

If recursion was supported in return!/yield! it would allow to avoid buffering the async sequences before yielding the rendered content.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants