{"record":{"id":"6c8ac44f63d96e3e","repo":"louthy/language-ext","slug":"bottomexception","errorCode":null,"errorMessage":"BottomException","messagePattern":"BottomException","errorType":"exception","errorClass":"BottomException","httpStatus":null,"severity":"error","filePath":"LanguageExt.Core/Traits/Monads/Monad/Monad.Module.cs","lineNumber":215,"sourceCode":"    /// and the users of `Monad.recur` would rightly be miffed if an implementation yielded a\n    /// stack-overflow, so use this function with caution.\n    /// </summary>\n    /// <param name=\"value\">Initial value to start the recursive process</param>\n    /// <param name=\"f\">Bind function that returns a monad with the bound value wrapped by `Next`, which\n    /// enables decision-making about whether to recur, or not.</param>\n    /// <typeparam name=\"M\">Monad type</typeparam>\n    /// <typeparam name=\"A\">Loop value</typeparam>\n    /// <typeparam name=\"B\">Done value</typeparam>\n    /// <returns>Monad structure</returns>\n    /// <exception cref=\"BottomException\"></exception>\n    [Pure]\n    public static K<M, B> unsafeRecur<M, A, B>(A value, Func<A, K<M, Next<A, B>>> f)\n        where M : Monad<M> =>\n        f(value).Bind(n => n switch\n                           {\n                               { IsLoop: true, Loop: var v } => unsafeRecur(v, f),\n                               { IsDone: true, Done: var v } => M.Pure(v),\n                               _                                  => throw new BottomException()\n                           });\n\n    /// <summary>\n    /// Allow for tail-recursion by using a trampoline function that returns a monad with the bound value\n    /// wrapped by `Next`, which enables decision-making about whether to keep the computation going or not.  \n    /// </summary>\n    /// <remarks>\n    /// This is a handy pre-built version of `Monad.Recur` that works with `Iterable` (a lazy stream that supports\n    /// both synchronicity and asynchronicity).  The `Natural` and `CoNatural` constraints allow any type that can\n    /// convert to and from `Iterable` to gain this prebuilt stack-protecting recursion.  \n    /// </remarks>\n    /// <param name=\"value\">Initial value to start the recursive process</param>\n    /// <param name=\"f\">Bind function that returns a monad with the bound value wrapped by `Next`, which\n    /// enables decision-making about whether to recur, or not.</param>\n    /// <typeparam name=\"M\">Monad type</typeparam>\n    /// <typeparam name=\"A\">Loop value</typeparam>\n    /// <typeparam name=\"B\">Done value</typeparam>\n    /// <returns>Monad structure</returns>","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/louthy/language-ext/blob/2f0e3628242889774d4141960a35671a0280051f/LanguageExt.Core/Traits/Monads/Monad/Monad.Module.cs#L197-L233","documentation":"unsafeRecur implements a trampoline for tail-recursive monadic computation: the bound function must return a Next<A,B> that is either Loop (continue) or Done (finish). If the Next value is neither (an invalid/default Next state), the library cannot proceed and throws BottomException, LanguageExt's signal for a non-terminable / undefined computation state.","triggerScenarios":"Calling Monad.Module.unsafeRecur (directly or via the Recur/TailRec helpers) with a function f that returns a Next<A,B> constructed as a default/invalid instance, or one whose discriminators IsLoop/IsDone are both false — typically from misuse of the Next type's constructors or custom Next implementations.","commonSituations":"Writing custom trampolined recursion and manually constructing Next values instead of using the provided Loop/Done constructors; switching on Next incorrectly and falling through to a bare default; library-version mismatches where Next's shape changed.","solutions":["Return Next.Loop(v, f) or Next.Done(v) exclusively from the recursive function — never a default(Next<A,B>) or partially initialized value.","Use the library's Recur/TailRec helper APIs instead of constructing Next values manually.","Inspect the value passed in the failing iteration; a non-deterministic branch may be skipping both constructors.","Verify LanguageExt package versions are consistent across the solution."],"exampleFix":"// before\nFunc<int, K<M, Next<int, int>>> f = n =>\n    n > 0 ? default(Next<int, int>) : Next<int, int>.Done(0);\n// after\nFunc<int, K<M, Next<int, int>>> f = n =>\n    n > 0 ? Next<int, int>.Loop(n - 1) : Next<int, int>.Done(0);","handlingStrategy":"validation","validationCode":"// Ensure the recursive function only ever returns Loop or Done\nvar next = f(value);\nif (!next.IsLoop && !next.IsDone) throw new ArgumentException(\"f must return Next.Loop or Next.Done\");","typeGuard":"bool IsValidNext<A, B>(Next<A, B> n) => n.IsLoop || n.IsDone;","tryCatchPattern":"try { return Monad<M>.unsafeRecur(value, f); }\ncatch (BottomException ex) { throw new InvalidOperationException(\"Trampoline function returned an invalid Next state (neither Loop nor Done)\", ex); }","preventionTips":["Only construct Next values via Next.Loop / Next.Done factory methods","Never return default(Next<A,B>) from a trampoline function","Cover every branch of the recursion with a unit test until Done is reached","Prefer the higher-level Recur/TailRec helpers over raw unsafeRecur"],"tags":["trampoline","monad","recursion","languageext"],"backgroundTag":"internal-invariant-violation","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"}