Repository navigation
Open
Labels
blocked: awaiting-fsharp-featureThere's a change to F# compiler or library needed to implement this featureThere's a change to F# compiler or library needed to implement this featureperformancePerformance questions, improvements and fixesPerformance questions, improvements and fixes
Milestone
Description
We've removed the tail recursion because it was hard to consolidate it into yield! (it was instead done with return!, which has no place in taskSeq).
However, today I helped someone with some code that he considered for taskSeq which had the following approach:
let getPoliciesAsync policyid =
asyncSeq{
use connection = new NpgsqlConnection(npgsqlConnectionStringBuilder.ConnectionString)
use command = new NpgsqlCommand($"SELECT policy_data FROM policy.tbl_policy where policy_id = {Sql.uuid policyid};", connection)
let! reader = command.ExecuteReaderAsync() |> Async.AwaitTask
let rec someRec() = asyncSeq{
let! rowExists = reader.ReadAsync() |> Async.AwaitTask
if rowExists then
yield {| dt1 = reader.GetString(0) |}
yield! someRec()
}
yield! someRec()
} |> AsyncSeq.toAsyncEnumAs you can see, it uses asyncSeq, but also: it is recursive. The same approach with taskSeq would likely be more performant, however, if there are a lot of rows, this becomes problematic. This code can be rewritten with a loop, though.
@dsyme, sharing this with you in case we want to revisit this at some point.
Reacted by Natalie Perret
Metadata
Metadata
Assignees
Labels
blocked: awaiting-fsharp-featureThere's a change to F# compiler or library needed to implement this featureThere's a change to F# compiler or library needed to implement this featureperformancePerformance questions, improvements and fixesPerformance questions, improvements and fixes
Activity
dsyme commentedon Nov 1, 2022
Yes, this should be rewritten with a loop
dsyme commentedon Nov 5, 2022
@abelbraaksma BTW I think I am going to prioritise an F# 8 addition to allow builders to specify
ReturnFromTailcallorYieldFromTailcallmethods.abelbraaksma commentedon Nov 6, 2022
@dsyme that would certainly simplify an addition like this, and would also allow
taskto support tail calls. Awesome! We can do some early-adoption testing here throughTaskSeq.abelbraaksma commentedon Oct 29, 2023
See fsharp/fslang-suggestions#1006 instead.
majocha commentedon Aug 1, 2025
This is now implemented,
yield!desugars toYieldFromFinalin tail call positions: dotnet/fsharp#18804Needs RFC, though.
abelbraaksma commentedon Nov 14, 2025
@majocha, thanks for letting me know! I only found out myself with the release of F# 10. Definitely going to give this a go.
dsyme commentedon Mar 8, 2026
/repo-assist let's make use of this, make a PR to add YieldFromFinal and implement a tailall transfer in the state machine driver. If you can work that out I'll be impressed
Add YieldFromFinal overloads for F# 10 compatibility (#62)
github-actions commentedon Mar 8, 2026
Pull request created: #310
dsyme commentedon Mar 8, 2026
Added the F# bug about YieldFromFinal here: dotnet/fsharp#19402
We can't make progress on this until that's fixed
github-actions commentedon Mar 14, 2026
[-]Consider tail recursion[/-][+]Tail recursion for TaskSeq[/+]jberzy commentedon Jul 16, 2026
Hello,
Now that the upstream fix is in, is it possible that we reintroduce tail recursion support?
Thanks
github-actions commentedon Jul 31, 2026
🤖 This is an automated response from Repo Assist.
Thanks for the ping,
@jberzy! You're right — the upstream F# fix (dotnet/fsharp#19402, implementingYieldFromFinal) has landed in F# 10.However, actually enabling tail-call transfer in
taskSeqis non-trivial. The challenge (as analysed in PR #380 which implemented thetaskSeqDynamicresumable path) is that theYieldFromFinaloverload is invoked by the compiler in all tail-call positions ofyield!, including calls inside loop bodies (e.g.for c in source do yield! f c). At that call site there is no way to distinguish a genuine "final" call (where we could hand off the enumerator permanently) from a within-loop tail call (where we must not, or we'd lose all remaining iterations).The safe incremental step would be to add the
YieldFromFinaloverload stubs to the builder so F# 10 compiles without warning, delegating toYieldFromfor now. The actual optimization can come later once a reliable way to discriminate call sites is available (perhaps via source-level annotation or a future compiler hint).@abelbraaksma— would you like me to open a draft PR that adds theYieldFromFinal/YieldFromFinalAsyncstubs to theTaskSeqBuilder, so F# 10 users at least get clean compilation? I can do that on request.Add this agentic workflow to your repo
To install this agentic workflow, run