louthy/language-ext · error · ExceptionalException
-2000000009
-2000000009
Error message
The IO monad is not in the monad-transformer stack or MonadIO.ToIO has not been implemented in the trait implementation for your monad-type. Therefore it's not possible to leverage `MonadIO` unlifting trait functionality. To resolve this, implement `MonadIO.ToIO` and/or `MonadIO.MapIO`.
What it means
MonadUnliftIO.ToIOMaybe's default implementation throws ExceptionalException (code -2000000009) because the monad M (or its stack) does not implement MonadIO.ToIO/MapIO, so the library cannot extract the IO monad from within M. Unlifting requires explicit support in the trait implementation.
Solutions
- Override MonadIO.ToIO and/or MonadIO.MapIO in the trait implementation for M
- Restructure so the IO monad is the outermost layer and can be run directly
- Replace unlift-style code with lift-style code (lift the final effect into M)
Example fix
// before var io = ToIOMaybe(ma); // ExceptionalException -2000000009 // after // in trait impl for M: public K<M, IO<A>> ToIO<A>(K<M, A> ma) => Map(ma, IO.pure);
Defensive patterns
Strategy: try-catch
Validate before calling
// static check: MonadIO<M> overrides ToIO/MapIO before calling ToIOMaybe
Type guard
bool CanUnliftIO<M>() => typeof(MonadIO<M>).GetMethod("ToIO")!.DeclaringType != typeof(MonadIO<M>); Try / catch
try { return ToIOMaybe(ma); }
catch (ExceptionalException ex) when (ex.Code == Errors.ToIONotSupported.Code) { /* restructure to lift-style */ } Prevention
- Implement ToIO/MapIO when the monad wraps IO
- Avoid generic unlift assumptions in monad-polymorphic code
- Prefer lifting effects out rather than unlifting IO from within
When it happens
Trigger: Calling ToIOMaybe<A>(K<M, A>) (or MapIOMaybe) on a monad whose trait impl lacks ToIO/MapIO overrides.
Common situations: Trying to run an IO action embedded inside a custom monad or transformer stack without an IO layer; generic monadic code that assumes unlift capability.
Related errors
- -2000000007
- 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
- Don't use Equals - use either RecordType
AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15).
Data as JSON: /api/errors/1772433d2169259b.
Report an issue: GitHub.
Appendix: source
Thrown at LanguageExt.Core/Traits/Maybe Traits/MonadUnliftIO/MonadUnliftIO.Trait.cs:30
public interface MonadUnliftIO<M> : Maybe.MonadIO<M>
where M : MonadUnliftIO<M>, Traits.Monad<M>
{
/// <summary>
/// Extract the IO monad from within the M monad (usually as part of a monad-transformer stack).
/// </summary>
/// <remarks>
/// IMPLEMENTATION REQUIRED: If this method isn't overloaded in this monad
/// or any monad in the stack on the way to the inner-monad, then it will throw
/// an exception.
///
/// This isn't ideal, it appears to be the only way to achieve this
/// kind of functionality in C# without resorting to magic.
/// </remarks>
/// <exception cref="ExceptionalException">If this method isn't overloaded in
/// the inner monad or any monad in the stack on the way to the inner-monad,
/// then it will throw an exception.</exception>
public static virtual K<M, IO<A>> ToIOMaybe<A>(K<M, A> ma) =>
throw new ExceptionalException(Errors.ToIONotSupported);
/// <summary>
/// Extract the IO monad from within the `M` monad (usually as part of a monad-transformer stack). Then perform
/// a mapping operation on the IO action before lifting the IO back into the `M` monad.
/// </summary>
public static virtual K<M, B> MapIOMaybe<A, B>(K<M, A> ma, Func<IO<A>, IO<B>> f) =>
M.ToIOMaybe(ma).Bind(io => M.LiftIOMaybe(f(io)));
/// <summary>
/// Queue this IO operation to run on the thread-pool.
/// </summary>
/// <param name="timeout">Maximum time that the forked IO operation can run for. `None` for no timeout.</param>
/// <returns>Returns a `ForkIO` data-structure that contains two IO effects that can be used to either cancel
/// the forked IO operation or to await the result of it.
/// </returns>
public static virtual K<M, ForkIO<A>> ForkIOMaybe<A>(K<M, A> ma, Option<TimeSpan> timeout = default) =>
M.MapIOMaybe(ma, io => io.Fork(timeout));
View on GitHub (pinned to 2f0e362824)