louthy/language-ext · error · BottomException
BottomException
Error message
BottomException
What it means
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.
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.
Example fix
// before
Func<int, K<M, Next<int, int>>> f = n =>
n > 0 ? default(Next<int, int>) : Next<int, int>.Done(0);
// after
Func<int, K<M, Next<int, int>>> f = n =>
n > 0 ? Next<int, int>.Loop(n - 1) : Next<int, int>.Done(0); Defensive patterns
Strategy: validation
Validate before calling
// Ensure the recursive function only ever returns Loop or Done
var next = f(value);
if (!next.IsLoop && !next.IsDone) throw new ArgumentException("f must return Next.Loop or Next.Done"); Type guard
bool IsValidNext<A, B>(Next<A, B> n) => n.IsLoop || n.IsDone;
Try / catch
try { return Monad<M>.unsafeRecur(value, f); }
catch (BottomException ex) { throw new InvalidOperationException("Trampoline function returned an invalid Next state (neither Loop nor Done)", ex); } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Option is not in a Some state
- Bug in MonadLaw or Monad.unsafeRecur. Contact language-ext…
- Ord attribute should have a struct type that derives from…
- Hashable attribute should have a struct type that derives…
- Don't use Equals - use either RecordType
AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15).
Data as JSON: /api/errors/6c8ac44f63d96e3e.
Report an issue: GitHub.
Appendix: source
Thrown at LanguageExt.Core/Traits/Monads/Monad/Monad.Module.cs:215
/// and the users of `Monad.recur` would rightly be miffed if an implementation yielded a
/// stack-overflow, so use this function with caution.
/// </summary>
/// <param name="value">Initial value to start the recursive process</param>
/// <param name="f">Bind function that returns a monad with the bound value wrapped by `Next`, which
/// enables decision-making about whether to recur, or not.</param>
/// <typeparam name="M">Monad type</typeparam>
/// <typeparam name="A">Loop value</typeparam>
/// <typeparam name="B">Done value</typeparam>
/// <returns>Monad structure</returns>
/// <exception cref="BottomException"></exception>
[Pure]
public static K<M, B> unsafeRecur<M, A, B>(A value, Func<A, K<M, Next<A, B>>> f)
where M : Monad<M> =>
f(value).Bind(n => n switch
{
{ IsLoop: true, Loop: var v } => unsafeRecur(v, f),
{ IsDone: true, Done: var v } => M.Pure(v),
_ => throw new BottomException()
});
/// <summary>
/// Allow for tail-recursion by using a trampoline function that returns a monad with the bound value
/// wrapped by `Next`, which enables decision-making about whether to keep the computation going or not.
/// </summary>
/// <remarks>
/// This is a handy pre-built version of `Monad.Recur` that works with `Iterable` (a lazy stream that supports
/// both synchronicity and asynchronicity). The `Natural` and `CoNatural` constraints allow any type that can
/// convert to and from `Iterable` to gain this prebuilt stack-protecting recursion.
/// </remarks>
/// <param name="value">Initial value to start the recursive process</param>
/// <param name="f">Bind function that returns a monad with the bound value wrapped by `Next`, which
/// enables decision-making about whether to recur, or not.</param>
/// <typeparam name="M">Monad type</typeparam>
/// <typeparam name="A">Loop value</typeparam>
/// <typeparam name="B">Done value</typeparam>
/// <returns>Monad structure</returns>View on GitHub (pinned to 2f0e362824)