Implementing ToIO on "custom" Transformer Stacks #1435
Replies: 3 comments 7 replies
-
Doesn't it always match to default, because you are switching on |
Beta Was this translation helpful? Give feedback.
-
I see that you have a generic parameter of So, make sure you've overrided the correct method in the trait-implementation of |
Beta Was this translation helpful? Give feedback.
-
So, part of the issue here is that However, So, to make this work, first implement these methods for public record FlowError
{
public static FlowError FromError(Error err) => throw new System.NotImplementedException();
public Error ToError() => throw new System.NotImplementedException();
} You would need these anyway, because you really want to capture exceptions from the inner Then you can do the public record Flow<A>(ReaderT<FlowState, EitherT<FlowError, IO>, A> executeFlow)
: K<Flow, A>;
public static class FlowExtensions
{
public static Flow<A> As<A>(this K<Flow, A> ma) =>
(Flow<A>)ma;
public static IO<Either<FlowError, A>> Run<A>(this K<Flow, A> ma, FlowState state) =>
ma.As()
.executeFlow
.Run(state)
.Run()
.Catch(_ => true, e => Either<FlowError, A>.Left(FlowError.FromError(e)))
.As();
}
public class Flow :
Monad<Flow>,
Readable<Flow, FlowState>,
Fallible<FlowError, Flow>
{
static K<Flow, B> Monad<Flow>.Bind<A, B>(K<Flow, A> ma, Func<A, K<Flow, B>> f) =>
new Flow<B>(ma.As().executeFlow.Bind(x => f(x).As().executeFlow));
static K<Flow, B> Functor<Flow>.Map<A, B>(Func<A, B> f, K<Flow, A> ma) =>
new Flow<B>(ma.As().executeFlow.Map(f));
static K<Flow, A> Applicative<Flow>.Pure<A>(A value) =>
new Flow<A>(ReaderT.pure<FlowState, EitherT<FlowError, IO>, A>(value));
static K<Flow, B> Applicative<Flow>.Apply<A, B>(K<Flow, Func<A, B>> mf, K<Flow, A> ma) =>
new Flow<B>(mf.As().executeFlow.Apply(ma.As().executeFlow).As());
static K<Flow, A> Readable<Flow, FlowState>.Asks<A>(Func<FlowState, A> f) =>
new Flow<A>(ReaderT.asks<EitherT<FlowError, IO>, A, FlowState>(f));
static K<Flow, A> Readable<Flow, FlowState>.Local<A>(Func<FlowState, FlowState> f, K<Flow, A> ma) =>
new Flow<A>(ReaderT.local(f, ma.As().executeFlow));
static K<Flow, A> Fallible<FlowError, Flow>.Fail<A>(FlowError error) =>
new Flow<A>(ReaderT.lift<FlowState, EitherT<FlowError, IO>, A>(EitherT<FlowError, IO, A>.Left(error)));
static K<Flow, A> Fallible<FlowError, Flow>.Catch<A>(
K<Flow, A> fa,
Func<FlowError, bool> Predicate,
Func<FlowError, K<Flow, A>> Fail) =>
new Flow<A>(
ReaderT<FlowState, EitherT<FlowError, IO>, A>.AsksM(
state =>
{
// Catch and process IO errors
var io = fa.As()
.executeFlow
.Run(state)
.Run()
.Catch<IO, Either<FlowError, A>>(
Predicate: (Error e) => Predicate(FlowError.FromError(e)),
Fail: (Error e) => Fail(FlowError.FromError(e)).As().Run(state));
// Catch and process FlowErrors
io = io.Bind(ea => ea switch
{
Either.Left<FlowError, A> (var l) => IO.pure(Either<FlowError, A>.Left(l)),
Either.Right<FlowError, A> (var r) => IO.pure(Either<FlowError, A>.Right(r))
});
// Return
return EitherT<FlowError, IO, A>.Lift(io);
}));
static K<Flow, A> MonadIO<Flow>.LiftIO<A>(IO<A> ma) =>
new Flow<A>(
ReaderT.lift<FlowState, EitherT<FlowError, IO>, A>(
new EitherT<FlowError, IO, A>(
IO.lift(envIO =>
{
try
{
var result = ma.Run(envIO);
return Either<FlowError, A>.Right(result);
}
catch (Exception e)
{
// When lifting random IO computations into Flow, always true to convert
// the exceptions into FlowError
return Either<FlowError, A>.Left(FlowError.FromError(e));
}
}))));
static K<Flow, IO<A>> MonadIO<Flow>.ToIO<A>(K<Flow, A> ma) =>
new Flow<IO<A>>(
ReaderT.asksM<EitherT<FlowError, IO>, FlowState, IO<A>>(
state =>
EitherT<FlowError, IO, IO<A>>.Right(
ma.As()
.executeFlow
.Run(state)
.As()
.Run()
.Map(ea => ea switch
{ // We must throw here so we can catch later
Either.Left<FlowError, A> (var l) => l.ToError().Throw<A>(),
Either.Right<FlowError, A> (var r) => r
})
.As())));
} Notice:
I haven't run any of the code above, so I don't know if it definitely works, but it should do. By the way, most of these issues go away if you just use the This is with public static class FlowExtensions
{
public static Flow<A> As<A>(this K<Flow, A> ma) =>
(Flow<A>)ma;
public static IO<A> Run<A>(this K<Flow, A> ma, FlowState state) =>
ma.As()
.executeFlow
.Run(state)
.As();
}
public class Flow :
Monad<Flow>,
Readable<Flow, FlowState>,
Fallible<Flow>
{
static K<Flow, B> Monad<Flow>.Bind<A, B>(K<Flow, A> ma, Func<A, K<Flow, B>> f) =>
new Flow<B>(ma.As().executeFlow.Bind(x => f(x).As().executeFlow));
static K<Flow, B> Functor<Flow>.Map<A, B>(Func<A, B> f, K<Flow, A> ma) =>
new Flow<B>(ma.As().executeFlow.Map(f));
static K<Flow, A> Applicative<Flow>.Pure<A>(A value) =>
new Flow<A>(ReaderT.pure<FlowState, IO, A>(value));
static K<Flow, B> Applicative<Flow>.Apply<A, B>(K<Flow, Func<A, B>> mf, K<Flow, A> ma) =>
new Flow<B>(mf.As().executeFlow.Apply(ma.As().executeFlow).As());
static K<Flow, A> Readable<Flow, FlowState>.Asks<A>(Func<FlowState, A> f) =>
new Flow<A>(ReaderT.asks<IO, A, FlowState>(f));
static K<Flow, A> Readable<Flow, FlowState>.Local<A>(Func<FlowState, FlowState> f, K<Flow, A> ma) =>
new Flow<A>(ReaderT.local(f, ma.As().executeFlow));
static K<Flow, A> Fallible<Error, Flow>.Fail<A>(Error error) =>
new Flow<A>(ReaderT.lift<FlowState, IO, A>(IO.fail<A>(error)));
static K<Flow, A> Fallible<Error, Flow>.Catch<A>(
K<Flow, A> fa,
Func<Error, bool> Predicate,
Func<Error, K<Flow, A>> Fail) =>
from state in Readable.ask<Flow, FlowState>()
from result in fa.MapIO(io => io.Catch(Predicate, e => Fail(e).Run(state)))
select result;
static K<Flow, A> MonadIO<Flow>.LiftIO<A>(IO<A> ma) =>
new Flow<A>(ReaderT.liftIO<FlowState, IO, A>(ma));
static K<Flow, IO<A>> MonadIO<Flow>.ToIO<A>(K<Flow, A> ma) =>
new Flow<IO<A>>(ma.As().executeFlow.ToIO().As());
} Notice how much simpler |
Beta Was this translation helpful? Give feedback.
-
Hi, I've been messing around (dipping my head into) with Language.Ext for a while and my next project is retries (or unlocking the potential of the IO stuff).
Originally I had the following setup (see discussion):
record Flow<A>(StateT<FlowState, EitherT<Error, IO>, A> executeFlow) : K<FlowEither, A>
However, I read in Discussion 1269 that the
StateT
transformer cannot work around the interface restriction ofMonadIO
in a senseful ("magicless") way, so for now I exchangedStateT
withReaderT
(as it would be sufficient for my main usecase).->
record Flow<A>(ReaderT<FlowState, EitherT<FlowError, IO>, A> executeFlow)
Still I'm getting the error that
ToIO
is not implemented, presumably because it's also not there onEitherT
.Following Paul's example in the discussion, which can also be found here, I tried implementing it on
Flow
myself:It compiles! But it always returns a wrapped bottom error, as the intermediate result is wrapped in
Pure
and the pattern matching does not seem to work.While I continue banging my head at this; Is this going into the right (hehe) direction?
Apologies if there is crucial code missing, I think all of the referenced functions are coming from the core library.
Cheers
Olli
Beta Was this translation helpful? Give feedback.
All reactions