You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Computation expressions like task, seq, taskSeq, async, coroutine often need to support tailcalls in the computational structure, e.g.
let recf n =seq{...do something...;if n >0thenyield! 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.
The text was updated successfully, but these errors were encountered:
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.
Computation expressions like task, seq, taskSeq, async, coroutine often need to support tailcalls in the computational structure, e.g.
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 thatreturn!
andyield!
are often used in non-tailcall positions, e.g. inside a try/with or try/finally, or sequencing ofyield!
. 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!
toReturnFromFinal
/YieldFromFinal
(or some other name) when they occur at the natural tailcall position if the method is present on the computation expression builder. Likewisedo!
in the final position can translate toReturnFromFinal
if present.The text was updated successfully, but these errors were encountered: