Replies: 2 comments
-
You don't seem to be following much of the advice I've given so far? The previous response about packaging up the subsystems with bespoke monadic types is really the way to go. It looks like you're mixing:
This approach is not gaining you anything. The code is neither declarative nor is it easy to write. I may well have misunderstood your code, which if that's the case, I apologise, that's just my inference from what I see above. I haven't use ASP.NET Core in anger yet, so maybe I'm missing something here. But anything that involves magic-DI is bad IMHO. Try to separate your FP code from anything to do with ASP.NET, simply package up the data needed into state that can be passed to the pure functions. The Game monad in the Really, you should take the exact same approach as you would in Haskell:
Taking the approach of mixing and matching is going to create the In terms of dependency-injection. There are a number of approaches, but the most purely-functional would be using the If you need DI and state, then you could use the If we first create some data-types to hold the environment and state: public record AppEnv;
public record AppState(DocumentState Document);
public record DocumentState(Option<DocumentDbContext> Context); And then create an public record App<M, A>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A> runApp) : K<App<M>, A>
where M : Monad<M>
{
// Methods below copied from previous response and their type names changed...
public static App<M, A> Pure(A value) => App<M>.Pure(value).As();
public static App<M, A> Fail(Error value) => App.lift<M, A>(Left(value)).As();
public static App<M, A> LiftIO(IO<A> ma) => App.liftIO<M, A>(ma).As();
public App<M, B> Map<B>(Func<A, B> f) => this.Kind().Map(f).As();
public App<M, B> Select<B>(Func<A, B> f) => this.Kind().Map(f).As();
public App<M, A> MapFail(Func<Error, Error> f) => this.Kind().Catch(f).As();
public App<M, B> Bind<B>(Func<A, K<App<M>, B>> f) => this.Kind().Bind(f).As();
public App<M, B> Bind<B>(Func<A, K<IO, B>> f) => this.Kind().Bind(f).As();
public App<M, C> SelectMany<B, C>(Func<A, K<App<M>, B>> b, Func<A, B, C> p) => this.Kind().SelectMany(b, p).As();
public App<M, C> SelectMany<B, C>(Func<A, K<IO, B>> b, Func<A, B, C> p) => this.Kind().SelectMany(b, p).As();
public static implicit operator App<M, A>(Pure<A> pure) => Pure(pure.Value);
public static implicit operator App<M, A>(Error fail) => Fail(fail);
public static implicit operator App<M, A>(Fail<Error> fail) => Fail(fail.Value);
public static App<M, A> operator |(App<M, A> ma, K<App<M>, A> mb) => ma.Choose(mb).As();
public static App<M, A> operator |(App<M, A> ma, Error mb) => ma.Choose(Fail(mb)).As();
public static App<M, A> operator |(App<M, A> ma, CatchM<Error, App<M>, A> mb) => ma.Catch(mb).As();
public static App<M, A> operator >> (App<M, A> ma, K<App<M>, A> mb) => ma.Bind(_ => mb);
public static App<M, A> operator >> (App<M, A> ma, K<IO, A> mb) => ma.Bind(_ => mb);
public static App<M, Unit> operator >> (App<M, A> ma, K<App<M>, Unit> mb) => ma.Bind(_ => mb);
public static App<M, Unit> operator >> (App<M, A> ma, K<IO, Unit> mb) => ma.Bind(_ => mb);
} And then implement its traits: public class App<M> :
MonadT<App<M>, M>, // Monad transformer
Choice<App<M>>, // Allows for failure coalescing
Fallible<App<M>>, // Allows for failure catching and mapping
Readable<App<M>, AppEnv>, // Enables access to the DI environment
Stateful<App<M>, AppState> // Enables a stateful value to be put and set
where M : Monad<M> // M allows us to lift any monad into the `App` transformer
{
public static K<App<M>, B> Bind<A, B>(K<App<M>, A> ma, Func<A, K<App<M>, B>> f) =>
new App<M, B>(ma.As().runApp.Bind(x => f(x).As().runApp));
public static K<App<M>, B> Map<A, B>(Func<A, B> f, K<App<M>, A> ma) =>
new App<M, B>(ma.As().runApp.Map(f));
public static K<App<M>, A> Pure<A>(A value) =>
new App<M, A>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.Pure(value));
public static K<App<M>, B> Apply<A, B>(K<App<M>, Func<A, B>> mf, K<App<M>, A> ma) =>
new App<M, B>(mf.As().runApp.Apply(ma.As().runApp));
public static K<App<M>, A> Lift<A>(K<M, A> ma) =>
new App<M, A>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.Lift(EitherT<Error, M, A>.Lift(ma)));
public static K<App<M>, Unit> Put(AppState value) =>
new App<M, Unit>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, Unit>.Put(value));
public static K<App<M>, Unit> Modify(Func<AppState, AppState> modify) =>
new App<M, Unit>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, Unit>.Modify(modify));
public static K<App<M>, A> Gets<A>(Func<AppState, A> f) =>
new App<M, A>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.Gets(f));
public static K<App<M>, A> Asks<A>(Func<AppEnv, A> f) =>
new App<M, A>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.Asks(f));
public static K<App<M>, A> Local<A>(Func<AppEnv, AppEnv> f, K<App<M>, A> ma) =>
new App<M, A>(ma.As().runApp.Local(f));
public static K<App<M>, A> LiftIO<A>(K<IO, A> ma) =>
new App<M, A>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.LiftIO(ma));
public static K<App<M>, A> Fail<A>(Error error) =>
new App<M, A>(RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.Lift(EitherT<Error, M, A>.Lift(error)));
public static K<App<M>, A> Catch<A>(K<App<M>, A> fa, Func<Error, bool> Predicate, Func<Error, K<App<M>, A>> Fail) =>
new App<M, A>(
new RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>(
inp =>
fa.As()
.runApp
.Run(inp.Env, inp.Output, inp.State)
.Catch(Predicate, e => Fail(e).As().runApp.Run(inp.Env, inp.Output, inp.State))));
public static K<App<M>, A> Choose<A>(K<App<M>, A> fa, K<App<M>, A> fb) =>
fa.Catch(fb);
} And create some static functions for interacting: public static class App
{
public static App<M, A> As<M, A>(this K<App<M>, A> ma)
where M : Monad<M> =>
(App<M, A>)ma;
public static App<M, A> lift<M, A>(K<M, A> ma)
where M : Monad<M> =>
new (RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.Lift(EitherT<Error, M, A>.Lift(ma)));
public static App<M, A> lift<M, A>(EitherT<Error, M, A> ma)
where M : Monad<M> =>
new (RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.Lift(ma));
public static App<M, A> lift<M, A>(Either<Error, A> ma)
where M : Monad<M> =>
lift(EitherT<Error, M, A>.Lift(ma));
public static App<M, A> liftIO<M, A>(IO<A> ma)
where M : Monad<M> =>
new (RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A>.LiftIO(ma));
public static App<M, AppEnv> getEnv<M>()
where M : Monad<M> =>
Readable.ask<App<M>, AppEnv>().As();
public static App<M, AppState> getState<M>()
where M : Monad<M> =>
Stateful.get<App<M>, AppState>().As();
public static App<M, Unit> putDocumentState<M>(DocumentState state)
where M : Monad<M> =>
Stateful.modify<App<M>, AppState>(app => app with { Document = state }).As();
public static App<M, Unit> modifyDocumentState<M>(Func<DocumentState, DocumentState> f)
where M : Monad<M> =>
Stateful.modify<App<M>, AppState>(app => app with { Document = f(app.Document) }).As();
public static App<M, DocumentState> getDocumentState<M>()
where M : Monad<M> =>
Stateful.gets<App<M>, AppState, DocumentState>(app => app.Document).As();
public static App<M, DocumentDbContext> documentDbContext<M>()
where M : Monad<M> =>
getDocumentState<M>()
.Bind(d => lift<M, DocumentDbContext>(d.Context.ToEither(Error.New("DbContext not set")))).As();
public static App<M, Unit> putDbContext<M>(DocumentDbContext ctx)
where M : Monad<M> =>
modifyDocumentState<M>(s => s with { Context = ctx });
public static App<M, Unit> clearDbContext<M>()
where M : Monad<M> =>
modifyDocumentState<M>(s => s with { Context = None });
public static App<M, A> withDbContext<M, A>(DocumentDbContext ctx, App<M, A> ma)
where M : Monad<M> =>
from _ in putDbContext<M>(ctx)
from r in ma
from x in clearDbContext<M>()
select r;
} Then I'm able to make your public static App<IO, Unit> HandleIncomingFileRequest(byte[] fileData, FileDetails fileDetails) =>
from res in DocumentRegistry.RegisterDocument(fileData, fileDetails)
from ctx in App.documentDbContext<IO>()
from _1 in DocumentRepository.AddDocumentAsync(ctx, res)
from _2 in StoreFileDataInBlobStorage(res)
select unit; You can see I've lifted the If you're a masochist you can avoid the above approach and use the wrapped type directly: RWST<AppEnv, Unit, AppState, EitherT<Error, M>, A> But it's a a lot of typing for every function that uses it and doesn't play well with refactoring! If you have a single Embedding |
Beta Was this translation helpful? Give feedback.
-
Some strange code I see for a "haskeller": everything is mostly imperative instead of computations 😄 public Either<Error, Unit> HandleIncomingFileRequest(byte[] fileData, FileDetails fileDetails) =>
from res1 in _documentRegistry.RegisterDocument(fileData, fileDetails)
from res2 in _documentRepository.AddDocumentAsync(_dbContext.Value, res1.Item1) // I know it probably returns something like `IO<Unit>`, but I'll pretend it returns `Either<Error, Unit>` for simplicity
from _2 in StoreFileDataInBlobStorage(res2.Item1)
select unit; |
Beta Was this translation helpful? Give feedback.
-
I have the following code:
and I want to be able to update the registry any time a new registry is created by registering a new document.
Right now, I am just returning a Unit from this function and the actual _documentRegistry is not being updated.
I want to do somehting like this:
I feel that I am doing something incorrectly when it comes to managing the state in my application. At some point we need to hold onto some state, and particularly in the service layer of my asp.net application is where I need to actually start loading information from the database and dealing with incoming requests.
Until this point it has made sense to use FP, but I'm finding myself being forced more and more into using imperative design in order to hold onto state and manage state changes, and so I think I am doing something wrong.
I'm wondering if I need to be using State and Func monads more effectively? For some reason in C# this just isn't clicking. Maybe because in Haskell all libraries are made to work like this and so I realistically haven't put too much thought into this stuff..
Do you have any suggestions or examples of how I should go about this? I'm at the application layer of my system now and that is when it all falls apart. The libraries are pure and functional, but I can't seem to figure this part out. in terms of integrating with the service layer of ASP.Net. I am using Dependency Injection to create this service and so I need to hold onto some State of the application at this layer, but I'm struggling figuring out how the State monad is initialized and updated, and GPT just keeps hallucinating 🫠
Appreciate any help you or the community can provide.
Thanks,
Kyle
Beta Was this translation helpful? Give feedback.
All reactions