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
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:
letgetPoliciesAsync 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 recsomeRec()= asyncSeq{let!rowExists= reader.ReadAsync()|> Async.AwaitTask
if rowExists thenyield{| dt1 = reader.GetString(0)|}yield! someRec()}yield! someRec()}|> AsyncSeq.toAsyncEnum
As 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.
@dsyme that would certainly simplify an addition like this, and would also allow task to support tail calls. Awesome! We can do some early-adoption testing here through TaskSeq.
/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
Thanks for the ping, @jberzy! You're right — the upstream F# fix (dotnet/fsharp#19402, implementing YieldFromFinal) has landed in F# 10.
However, actually enabling tail-call transfer in taskSeq is non-trivial. The challenge (as analysed in PR #380 which implemented the taskSeqDynamic resumable path) is that the YieldFromFinal overload is invoked by the compiler in all tail-call positions of yield!, 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 YieldFromFinal overload stubs to the builder so F# 10 compiles without warning, delegating to YieldFrom for 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 the YieldFromFinal/YieldFromFinalAsync stubs to the TaskSeqBuilder, so F# 10 users at least get clean compilation? I can do that on request.
Generated by 🌈 Repo Assist, see workflow run. Learn more. Comment /repo-assist to run again
Add this agentic workflow to your repo
To install this agentic workflow, run
gh aw add githubnext/agentics/workflows/repo-assist.md@42c2ab5b4e4c9273534c39259b2e0df7f20f07e9
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