{"record":{"id":"49117818ab933d33","repo":"louthy/language-ext","slug":"you-can-t-map-a-tail-call","errorCode":null,"errorMessage":"You can't map a tail call","messagePattern":"You can't map a tail call","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"LanguageExt.Core/Effects/IO/DSL/IOTail.cs","lineNumber":10,"sourceCode":"using System;\nusing System.Threading.Tasks;\nusing LanguageExt.Traits;\n\nnamespace LanguageExt.DSL;\n\nrecord 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}","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/louthy/language-ext/blob/2f0e3628242889774d4141960a35671a0280051f/LanguageExt.Core/Effects/IO/DSL/IOTail.cs#L1-L28","documentation":"IOTail<A> is an internal marker node in the LanguageExt IO DSL representing a tail-recursive call produced by the IO monad's trampoline. It is not a runnable effect, so Map cannot legally wrap it — mapping over a tail call would break the tail-call resolution that the trampoline performs in `resolve`. The library throws NotSupportedException deliberately to surface misuse of the DSL internals early instead of silently corrupting the recursion.","triggerScenarios":"Calling `.Map(f)` on an IO value that is actually an IOTail<A> — typically only reachable if you pattern-match into IO internals (DSL namespace) or call Map on the result of a tail-recursive `go` function before the trampoline resolves it.","commonSituations":"Developers writing custom IO interpreters or traversing the IO AST in LanguageExt.DSL; upgrading LanguageExt versions where IOTail became part of the public-ish DSL surface and code that previously saw IO monads now sees IOTail nodes.","solutions":["Do not call Map directly on the IOTail node; resolve the tail call first (via the trampoline / IO.run) and map on the resolved IO.","Pattern-match: if you have IOTail<A> t and need IO<B>, use t.Tail (the wrapped IO<A>) and Map over that, ensuring typeof(A)==typeof(B) constraints of resolve are respected.","Restructure your recursive IO so the transformation is applied inside the recursive step (e.g. map inside the Bind continuation) rather than over the tail-call boundary.","If you are inspecting the AST, skip or unwrap IOTail nodes explicitly before applying combinators."],"exampleFix":"// before\nIO<int> mapped = tailResult.Map(x => x * 2); // throws\n// after\nIO<int> resolved = tailResult switch\n{\n    IOTail<int> t => t.Tail,          // unwrap the tail call\n    var io => io\n};\nIO<int> mapped = resolved.Map(x => x * 2);","handlingStrategy":"type-guard","validationCode":"bool isMappable<A>(IO<A> io) => io is not IOTail<A>;","typeGuard":"IO<B> safeMap<A,B>(IO<A> io, Func<A,B> f) =>\n    io is IOTail<A> t ? t.Tail.Map(f) : io.Map(f);","tryCatchPattern":"try { result = io.Map(f); }\ncatch (NotSupportedException ex) when (ex.Message.Contains(\"tail call\"))\n{\n    result = ((IOTail<A>)io).Tail.Map(f);\n}","preventionTips":["Never apply combinators directly to nodes from LanguageExt.DSL; work with resolved IO values.","Add an IOTail type-check branch before generic Map calls in interpreter code.","Keep transformations inside recursive steps rather than outside tail-recursive loops."],"tags":["languageext","io-monad","tail-call","not-supported","dsl-internals"],"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"}