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.
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 toReturnFromFinalif present.