{"record":{"id":"18c93fa8e9c22b60","repo":"louthy/language-ext","slug":"bug-in-monadlaw-or-monad-unsaferecur-contact-language-ext","errorCode":null,"errorMessage":"Bug in MonadLaw or Monad.unsafeRecur. Contact language-ext maintainer via the repo.","messagePattern":"Bug in MonadLaw or Monad\\.unsafeRecur\\. Contact language-ext maintainer via the repo\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"LanguageExt.Core/Traits/Monads/Monad/Monad.Laws.cs","lineNumber":64,"sourceCode":"    {\n        equals ??= (fa, fb) => fa.Equals(fb);\n        return ApplicativeLaw<F>.validate(equals) >>>\n               leftIdentityLaw(equals)            >>>\n               rightIdentityLaw(equals)           >>>\n               associativityLaw(equals)           >>>\n               recurIsSameAsBind(equals);\n    }\n\n    public static Validation<Error, Unit> recurIsSameAsBind(Func<K<F, int>, K<F, int>, bool>? equals = null)\n    {\n        var example = Seq(1, 2, 3, 4, 5, 6, 7, 8, 9);\n\n        var result1 = Monad.recur((0, example), rec);\n        var result2 = Monad.unsafeRecur((0, example), rec);\n        var result3 = bind(example);\n\n        equals ??= (fa, fb) => fa.Equals(fb);\n        if(!equals(result1, result2)) throw new Exception(\"Bug in MonadLaw or Monad.unsafeRecur. Contact language-ext maintainer via the repo.\");\n        if (!equals(result1, result3))\n        {\n            return Validation.Fail<Error, Unit>(\n                Error.New($\"Monad trait implementation for {typeof(F).Name}.Recur gives a different \"    +\n                          $\"result to the equivalent recursive {typeof(F).Name}.Bind.  This suggests \"   +\n                          $\"an implementation bug, most likely in {typeof(F).Name}.Recur, but possibly \" +\n                          $\"in {typeof(F).Name}.Bind.\"));\n        }\n\n        return Validation.Success<Error, Unit>(unit);\n        \n        K<F, Next<(int Total, Seq<int> Values), int>> rec((int Total, Seq<int> Values) pair) =>\n            pair.Values switch\n            {\n                []          => F.Pure(Next.Done<(int, Seq<int>), int>(pair.Total)),\n                var (x, xs) => F.Pure(Next.Loop<(int, Seq<int>), int>((pair.Total + x, xs))) \n            };\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/louthy/language-ext/blob/2f0e3628242889774d4141960a35671a0280051f/LanguageExt.Core/Traits/Monads/Monad/Monad.Laws.cs#L46-L82","documentation":"Monad.Laws.recurIsSameAsBind validates that Monad.recur and Monad.unsafeRecur produce identical results; if they differ, this internal invariant is broken and the library throws asking the user to report it to the language-ext maintainer. It indicates a bug in the monad's Recur/unsafeRecur trait implementation, not user data.","triggerScenarios":"Running the Monad law-validation suite (via validate) on a monad F whose Recur/unsafeRecur trait methods are implemented inconsistently, so recur(...) != unsafeRecur(...).","commonSituations":"Writing a custom monad trait implementation with a faulty unsafeRecur (e.g. skipping the recur-loop protection or short-circuiting differently), then running the built-in law checks.","solutions":["Review and fix the F.Recur/unsafeRecur implementation so unsafeRecur matches recur's observable result","Report the bug with a repro to the language-ext GitHub repo as the message instructs","Bypass unsafeRecur in user code and use recur only until the bug is fixed"],"exampleFix":"// before\nstatic K<F, S, A> unsafeRecur<S, A>((S, A) state, Func<(S, A), K<F, (S, A)>> f) => bind(f(state), s => s.Item1 < 0 ? pure(s) : unsafeRecur(s, f)); // diverges from recur semantics\n// after\n// implement unsafeRecur exactly as recur but without the safety check, preserving result equality: same bind recursion, no early exit","handlingStrategy":"try-catch","validationCode":"// run the law validation in tests: MonadLaws.validate(monadInstance, examples) before shipping","typeGuard":null,"tryCatchPattern":"try { laws.Validate(); }\ncatch (Exception ex) when (ex.Message.StartsWith(\"Bug in MonadLaw\")) { reportToMaintainer(ex); }","preventionTips":["Run Monad law validations in unit tests for custom monads","Implement unsafeRecur to mirror recur exactly (minus safety checks)","Track language-ext releases for Recur bug fixes"],"tags":["csharp","languageext","monad","laws","internal"],"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"}