Replies: 4 comments
-
I see that you only have Terminal values ( The banking sample is particularly useful to understand the usage of Free monads. |
Beta Was this translation helpful? Give feedback.
-
@alarya Thanks for the reply. It was pretty simple to write a Please can you explain your second sentence a bit more. I'm still finding me feet with this stuff, and I don't really understand what else I need to implement. I get I'm looking at the banking sample, but it's quite hard to work out what's going on there when you're still learning. Thanks again, really appreciate any further explanation you can give. |
Beta Was this translation helpful? Give feedback.
-
Your code could look like the following. Let's say, the entity looks like this: public record Entity(Guid Id, DateTime LastUpdated, Guid LastUpdatedBy, string value); Your Free monad would define the two terminal methods and other IO methods to build continuations. [Free]
public interface IO<T>
{
[Pure] T PureT(T Value);
[Pure] T FailT(Error error);
Entity FindEntity(Guid id);
Unit UpdateEntity(Entity updated);
} You can define more complex operations using existing methods in the Free monad. Meaning, define only the very basic IO interactions in the Free monad. public static class Operations
{
public static IO<Unit> UpdateEntitySafely(Guid id, Entity beforeUpdate, Entity updated) =>
from current in IO.FindEntity(id)
from _1 in guard(current.LastUpdated > beforeUpdate.LastUpdated, Error.New("Some error")).ToIO()
from _2 in IO.UpdateEntity(updated)
select unit;
//These are convenience methods to be able to mix Either and IO
//returning methods in the same LINQ query.
public static IO<Unit> ToIO(this Guard<Error> g) =>
g.ToEither().Match(
Left: error => IO.FailT<Unit>(error),
Right: result => IO.PureT(result));
public static IO<R> ToIO<R>(this Either<Error, R> either) =>
either.Match(
Left: error => IO.FailT<R>(error),
Right: result => IO.PureT(result));
} As described by @louthy, you need an interpreter to execute the operations and this where your actual IO will happen. So you can call an actual DB here. It's also possible to give extra parameters here (see banking example). public class Interpreter
{
public static Either<Error, A> Interpret<A>(IO<A> ma) => ma switch
{
Pure<A> (var value) => Right<Error, A>(value),
Fail<A> (var error) => Left<Error, A>(error),
FindEntity<A> (var guid, var next) => FindEntity(guid, next),
UpdateEntity<A> (var updated, var next) => UpdateEntity(updated, next),
_ => throw new NotSupportedException()
};
public static Either<Error, A> FindEntity<A>(Guid guid, Func<Entity, IO<A>> next) =>
//Replace with an actual call to DB to retrieve Entity
from entity in Right(new Entity(Guid.Empty, DateTime.Now, Guid.Empty, ""))
from result in Interpret(next(entity))
select result;
public static Either<Error, A> UpdateEntity<A>(Entity updated, Func<Unit, IO<A>> next) =>
//Save the entity to actual Database
from result in Interpret(next(unit))
select result;
} This is how the calling code could look like: public class Controller
{
public IO<Unit> CallingCode(Guid id) =>
from entity in Interpreter.Interpret(from _ in IO.FindEntity(id) select _).ToIO()
let updated = entity with { value = "newValue" } //make some updates to entity
from _ in Operations.UpdateEntitySafely(id, entity, updated)
select unit;
} Hope this helps. |
Beta Was this translation helpful? Give feedback.
-
@alarya Wow, that's amazing! Need to read it a few more times to get my head around it, but I can see that it's a really good way of doing things. Thanks again. |
Beta Was this translation helpful? Give feedback.
-
My database entities all have a
LastUpdatedAt
column (datetime
) and aLastUpdatedByUserId
column. Whenever an entity is saved, these two are updated, so we can see who last changed the entity and when.I want to intercept database updates, and check if the database entity has been updated since the incoming one. If so, I don't want to save the incoming entity, but want to notify the calling code so that it can warn the user.
Based on a suggestion by @louthy, I'm trying to use the
[Free]
monad. If I've understood it correctly, I need to do something like this...My aim is to have something that I can use like this...
This is basically the same pattern as the
Match
method for other monads, but takes three delegates, one for each of the three possibilities forFallible
.However, the generated code for my new monad doesn't have a
Match
method. I can seeBind
,Flatten
andMap
, but noMatch
.Did I miss something, or do I need to write this myself?
Thanks
Beta Was this translation helpful? Give feedback.
All reactions