{"record":{"id":"ae4d059ea91b6a04","repo":"louthy/language-ext","slug":"tail-calls-can-t-transform-in-the-select","errorCode":null,"errorMessage":"Tail calls can't transform in the `select`","messagePattern":"Tail calls can't transform in the `select`","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"LanguageExt.Core/Effects/IO/DSL/IOTail.cs","lineNumber":25,"sourceCode":"record IOTail<A>(IO<A> Tail) : IO<A>\n{\n    public override IO<B> Map<B>(Func<A, B> f) => \n        throw new NotSupportedException(\"You can't map a tail call\");\n\n    public override IO<B> Bind<B>(Func<A, K<IO, B>> f) =>\n        throw new NotSupportedException(\"You can't chain a tail call\");\n\n    public override IO<B> BindAsync<B>(Func<A, ValueTask<K<IO, B>>> f) => \n        throw new NotSupportedException(\"You can't chain a tail call\");\n\n    public override string ToString() => \n        \"IO tail\";\n\n    public static IO<C> resolve<B, C>(A initialValue, IO<B> bindResult, Func<A, B, C> project)\n        => bindResult switch\n           {\n               IOTail<B> tail when typeof(B) == typeof(C) => (IO<C>)(object)tail.Tail,\n               IOTail<B> => throw new NotSupportedException(\"Tail calls can't transform in the `select`\"),\n               var mb => mb.Map(y => project(initialValue, y))\n           };\n}\n","sourceCodeStart":7,"sourceCodeEnd":29,"githubUrl":"https://github.com/louthy/language-ext/blob/2f0e3628242889774d4141960a35671a0280051f/LanguageExt.Core/Effects/IO/DSL/IOTail.cs#L7-L29","documentation":"IOTail.resolve trampolines a tail call whose bind result is an IOTail<B> by re-typing the inner Tail as IO<C> — but that fast path is only legal when B and C are the same type. If a `select`/`project` function attempts to transform the value (B != C) while the result is still a tail call, the library cannot perform the conversion, so it throws NotSupportedException.","triggerScenarios":"Using LINQ query syntax over IO where the `select` clause transforms the bound value (e.g. `from x in loop from y in ... select project(x, y)`) while the bind result remains an unresolved IOTail<B> and typeof(B) != typeof(C).","commonSituations":"Writing recursive IO with LINQ comprehension syntax (from/into/select) where the final select changes the result type; converting an IO loop that previously returned the same type to one returning a projected value.","solutions":["Move the transformation inside the recursion (apply project in the Bind continuation before the tail call) so the final select is type-preserving.","Make the select type-identical (B == C) — e.g. return the value unchanged in select and Map afterwards on the resolved IO.","Avoid `select` over tail-recursive loops; instead Bind the loop result and then Map on the resolved (non-tail) IO.","Restructure using explicit recursion with an accumulator so the projection happens per iteration, not via the tail call's select."],"exampleFix":"// before — throws when loop result is a tail call and types differ\nvar q = from x in loop\n        from y in step\n        select project(x, y);            // B != C\n// after\nIO<C> result = loop.Bind(x => step.Bind(y => IO.pure(project(x, y))));\n// projection now happens inside Bind, no tail-call select","handlingStrategy":"validation","validationCode":"// Ensure the select over a possibly-tail loop is type-preserving:\n// typeof(B) == typeof(C), or restructure the projection into a Bind.","typeGuard":"IO<C> safeSelect<B,C>(A init, IO<B> bindResult, Func<A,B,C> project) =>\n    typeof(B) == typeof(C)\n        ? IO<C>.pure((C)(object)default(B)) // type-preserving path\n        : bindResult.Bind(y => IO<C>.pure(project(init, y))); // project inside Bind","tryCatchPattern":"try { r = IOTail<A>.resolve(init, bindResult, project); }\ncatch (NotSupportedException ex) when (ex.Message.Contains(\"can't transform\"))\n{\n    r = bindResult.Bind(y => IO<C>.pure(project(init, y)));\n}","preventionTips":["Avoid `select` clauses that change the result type over tail-recursive IO loops.","Apply projections inside Bind continuations instead of in the final select.","Convert LINQ-comprehension loops to explicit recursive Bind + Map on resolved IO."],"tags":["languageext","io-monad","tail-call","linq","select"],"backgroundTag":"unsupported-operation","analyzedSha":"2f0e3628242889774d4141960a35671a0280051f","analyzedAt":"2026-09-15T03:31:55.716Z","contentChangedAt":"2026-09-15T03:31:55.716Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}