Repository navigation
Tail recursion for TaskSeq #62
Description
Activity
Yes, this should be rewritten with a loop
- addedperformancePerformance questions, improvements and fixesPerformance questions, improvements and fixes
on Nov 3, 2022 @abelbraaksma BTW I think I am going to prioritise an F# 8 addition to allow builders to specify
ReturnFromTailcallorYieldFromTailcallmethods.Reacted by Jimmy Byrd and Abel Braaksma@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.See fsharp/fslang-suggestions#1006 instead.
- addedblocked: 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 feature
on Oct 29, 2023 This is now implemented,
yield!desugars toYieldFromFinalin tail call positions: dotnet/fsharp#18804
Needs RFC, though.Reacted by Ruben Bartelink and Don Syme@majocha, thanks for letting me know! I only found out myself with the release of F# 10. Definitely going to give this a go.
/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
Reacted by github-actions- added a commit that references this issue
on Mar 8, 2026 Pull request created: #310
Generated by Repo Assist
Added the F# bug about YieldFromFinal here: dotnet/fsharp#19402
We can't make progress on this until that's fixed
github-actions commented
on Mar 14, 2026 on Mar 14, 2026 – with GitHub Actions · Hidden as outdatedshow commentMore actionsHello,
Now that the upstream fix is in, is it possible that we reintroduce tail recursion support?
Thanks
github-actions commented
on Jul 31, 2026 on Jul 31, 2026 – with GitHub ActionsContributorMore actions🤖 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.Generated by 🌈 Repo Assist, see workflow run. Learn more.
Comment /repo-assist to run againAdd this agentic workflow to your repo
To install this agentic workflow, run
gh aw add githubnext/agentics/workflows/repo-assist.md@42c2ab5b4e4c9273534c39259b2e0df7f20f07e9
We've removed the tail recursion because it was hard to consolidate it into
yield!(it was instead done withreturn!, which has no place intaskSeq).However, today I helped someone with some code that he considered for
taskSeqwhich had the following approach:As you can see, it uses
asyncSeq, but also: it is recursive. The same approach withtaskSeqwould 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.