Skip to content

Commit

Permalink
Decider implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
zsrdjan committed May 11, 2024
1 parent e6d0f63 commit 7cd433a
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/Fraktalio.FModel.Contracts/IDecider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Fraktalio.FModel.Contracts;

/// <summary>
/// Decider Interface
/// </summary>
/// <typeparam name="C">C Command</typeparam>
/// <typeparam name="S">S State</typeparam>
/// <typeparam name="E">E Event</typeparam>
public interface IDecider<in C, S, E>
{
Func<C, S, IEnumerable<E>> Decide { get; }
Func<S, E, S> Evolve { get; }
S InitialState { get; }
}
65 changes: 65 additions & 0 deletions src/Fraktalio.FModel/Decider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Fraktalio.FModel.Contracts;

namespace Fraktalio.FModel;

/// <summary>
/// [Decider] is a datatype that represents the main decision-making algorithm.
///
/// It has three generic parameters `C`, `S`, `E` , representing the type of the values that [Decider] may contain or use.
/// [Decider] can be specialized for any type `C` or `S` or `E` because these types does not affect its behavior.
/// [Decider] behaves the same for `C`=[Int] or `C`=`OddNumberCommand`.
/// </summary>
/// <param name="decide">A function/lambda that takes command of type [C] and input state of type [S] as parameters, and returns/emits the list of output events [E]</param>
/// <param name="evolve">A function/lambda that takes input state of type [S] and input event of type [E] as parameters, and returns the output/new state [S]</param>
/// <param name="initialState">A starting point / An initial state of type [S]</param>
/// <typeparam name="C">Command</typeparam>
/// <typeparam name="S">State</typeparam>
/// <typeparam name="E">Event</typeparam>
public class Decider<C, S, E>(Func<C, S, IEnumerable<E>> decide, Func<S, E, S> evolve, S initialState)
: IDecider<C, S, E>
{
public Func<C, S, IEnumerable<E>> Decide { get; } = decide;
public Func<S, E, S> Evolve { get; } = evolve;
public S InitialState { get; } = initialState;

/// <summary>
/// Left map on C/Command parameter - Contravariant
/// </summary>
/// <param name="f"></param>
/// <typeparam name="Cn"></typeparam>
/// <returns></returns>
public Decider<Cn, S, E> MapLeftOnCommand<Cn>(Func<Cn, C> f)
{
var internalDecider = new InternalDecider<C, S, S, E, E>(Decide, Evolve, InitialState);
var mappedInternalDecider = internalDecider.MapLeftOnCommand(f);
return mappedInternalDecider.AsDecider();
}

/// <summary>
/// Di-map on E/Event parameter
/// </summary>
/// <param name="fl"></param>
/// <param name="fr"></param>
/// <typeparam name="En"></typeparam>
/// <returns></returns>
public Decider<C, S, En> DimapOnEvent<En>(Func<En, E> fl, Func<E, En> fr)
{
var internalDecider = new InternalDecider<C, S, S, E, E>(Decide, Evolve, InitialState);
var mappedInternalDecider = internalDecider.DimapOnEvent(fl, fr);
return mappedInternalDecider.AsDecider();
}

/// <summary>
/// Di-map on S/State parameter
/// </summary>
/// <param name="fl"></param>
/// <param name="fr"></param>
/// <typeparam name="Sn"></typeparam>
/// <returns></returns>
public Decider<C, Sn, E> DimapOnState<Sn>(Func<Sn, S> fl, Func<S, Sn> fr)
{
var internalDecider = new InternalDecider<C, S, S, E, E>(Decide, Evolve, InitialState);
var mappedInternalDecider = internalDecider.DimapOnState(fl, fr);
return mappedInternalDecider.AsDecider();
}
}
37 changes: 37 additions & 0 deletions src/Fraktalio.FModel/DeciderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Fraktalio.FModel;

public static class DeciderExtensions
{
/// <summary>
/// Combine [Decider]s into one [Decider]
///
/// Possible to use when:
/// - [E] and [E2] have common superclass [E_SUPER]
/// - [C] and [C2] have common superclass [C_SUPER]
/// </summary>
/// <param name="x">First decider</param>
/// <param name="y">Second decider</param>
/// <typeparam name="C">Command type of the first Decider</typeparam>
/// <typeparam name="S">State type of the first Decider</typeparam>
/// <typeparam name="E">Event type of the first Decider</typeparam>
/// <typeparam name="C2">Command type of the second Decider</typeparam>
/// <typeparam name="S2">Input_State type of the second Decider</typeparam>
/// <typeparam name="E2">Event type of the second Decider</typeparam>
/// <typeparam name="C_SUPER">super type of the command types C and C2</typeparam>
/// <typeparam name="E_SUPER">super type of the E and E2 types</typeparam>
/// <returns>Combined decider</returns>
public static Decider<C_SUPER?, Tuple<S, S2>, E_SUPER?> Combine<C, S, E, C2, S2, E2, C_SUPER, E_SUPER>(
this Decider<C?, S, E?> x, Decider<C2?, S2, E2?> y)
where C : class, C_SUPER
where C2 : class, C_SUPER
where E : class, E_SUPER
where E2 : class, E_SUPER
{
var internalDeciderX = new InternalDecider<C?, S, S, E?, E?>(x.Decide, x.Evolve, x.InitialState);
var internalDeciderY = new InternalDecider<C2?, S2, S2, E2?, E2?>(y.Decide, y.Evolve, y.InitialState);
var combinedInternalDecider =
internalDeciderX.Combine<C?, S, S, E?, E?, C2, S2, S2, E2, E2, C_SUPER, E_SUPER?, E_SUPER?>(
internalDeciderY);
return combinedInternalDecider.AsDecider();
}
}
229 changes: 229 additions & 0 deletions src/Fraktalio.FModel/InternalDecider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
namespace Fraktalio.FModel;



/// <summary>
/// [InternalDecider] is a datatype that represents the main decision-making algorithm.
/// It has five generic parameters [C], [Si], [So], [Ei], [Eo] , representing the type of the values that [InternalDecider] may contain or use.
/// [InternalDecider] can be specialized for any type [C] or [Si] or [So] or [Ei] or [Eo] because these types does not affect its behavior.
/// [InternalDecider] behaves the same for [C]=[Int] or [C]=YourCustomType, for example.
///
/// [InternalDecider] is a pure domain component.
/// </summary>
/// <typeparam name="C">C Command type</typeparam>
/// <typeparam name="Si">Si Input State type</typeparam>
/// <typeparam name="So">Output State type</typeparam>
/// <typeparam name="Ei">Input Event type</typeparam>
/// <typeparam name="Eo">Output Event type</typeparam>
internal class InternalDecider<C, Si, So, Ei, Eo>
{
/// <summary>
/// [InternalDecider] is a datatype that represents the main decision-making algorithm.
/// It has five generic parameters [C], [Si], [So], [Ei], [Eo] , representing the type of the values that [InternalDecider] may contain or use.
/// [InternalDecider] can be specialized for any type [C] or [Si] or [So] or [Ei] or [Eo] because these types does not affect its behavior.
/// [InternalDecider] behaves the same for [C]=[Int] or [C]=YourCustomType, for example.
///
/// [InternalDecider] is a pure domain component.
/// </summary>
/// <typeparam name="C">C Command type</typeparam>
/// <typeparam name="Si">Si Input State type</typeparam>
/// <typeparam name="So">Output State type</typeparam>
/// <typeparam name="Ei">Input Event type</typeparam>
/// <typeparam name="Eo">Output Event type</typeparam>
internal InternalDecider(Func<C, Si, IEnumerable<Eo>> decide,
Func<Si, Ei, So> evolve,
So initialState)
{
Decide = decide;
Evolve = evolve;
InitialState = initialState;
}

/// <summary>
/// A function/lambda that takes command of type [C] and input state of type [Si] as parameters, and returns/emits the list of output events
/// </summary>
public Func<C, Si, IEnumerable<Eo>> Decide { get; }

/// <summary>
/// A function/lambda that takes input state of type [Si] and input event of type [Ei] as parameters, and returns the output/new state [So]
/// </summary>
public Func<Si, Ei, So> Evolve { get; }

/// <summary>
/// A starting point / An initial state of type [So]
/// </summary>
public So InitialState { get; }

/// <summary>
/// Left map on C/Command parameter - Contravariant
/// </summary>
/// <param name="f"></param>
/// <typeparam name="Cn">Cn Command new</typeparam>
/// <returns></returns>
public InternalDecider<Cn, Si, So, Ei, Eo> MapLeftOnCommand<Cn>(Func<Cn, C> f)
{
return new InternalDecider<Cn, Si, So, Ei, Eo>(
(cn, si) => Decide(f(cn), si),
(si, ei) => Evolve(si, ei),
InitialState
);
}

/// <summary>
/// Dimap on E/Event parameter - Contravariant on input event and Covariant on output event = Profunctor
/// </summary>
/// <param name="fl"></param>
/// <param name="fr"></param>
/// <typeparam name="Ein">Event input new</typeparam>
/// <typeparam name="Eon">Event output new</typeparam>
/// <returns></returns>
public InternalDecider<C, Si, So, Ein, Eon> DimapOnEvent<Ein, Eon>(Func<Ein, Ei> fl, Func<Eo, Eon> fr)
{
return new InternalDecider<C, Si, So, Ein, Eon>(
(c, si) => Decide(c, si).Select(fr),
(si, ein) => Evolve(si, fl(ein)),
InitialState
);
}

/// <summary>
/// Left map on E/Event parameter - Contravariant
/// </summary>
/// <param name="f"></param>
/// <typeparam name="Ein"></typeparam>
/// <returns></returns>
public InternalDecider<C, Si, So, Ein, Eo> MapLeftOnEvent<Ein>(Func<Ein, Ei> f)
{
return DimapOnEvent<Ein, Eo>(f, eo => eo);
}

/// <summary>
/// Right map on E/Event parameter - Covariant
/// </summary>
/// <param name="f"></param>
/// <typeparam name="Eon"></typeparam>
/// <returns></returns>
public InternalDecider<C, Si, So, Ei, Eon> MapOnEvent<Eon>(Func<Eo, Eon> f)
{
return DimapOnEvent<Ei, Eon>(ei => ei, f);
}

/// <summary>
/// Dimap on S/State parameter - Contravariant on input state (Si) and Covariant on output state (So) = Profunctor
/// </summary>
/// <param name="fl"></param>
/// <param name="fr"></param>
/// <typeparam name="Sin">State input new</typeparam>
/// <typeparam name="Son">State output new</typeparam>
/// <returns></returns>
public InternalDecider<C, Sin, Son, Ei, Eo> DimapOnState<Sin, Son>(Func<Sin, Si> fl, Func<So, Son> fr)
{
return new InternalDecider<C, Sin, Son, Ei, Eo>(
(c, sin) => Decide(c, fl(sin)),
(sin, ei) => fr(Evolve(fl(sin), ei)),
fr(InitialState)
);
}

/// <summary>
/// Left map on S/State parameter - Contravariant
/// </summary>
/// <param name="f"></param>
/// <typeparam name="Sin">Sin State input new</typeparam>
/// <returns></returns>
public InternalDecider<C, Sin, So, Ei, Eo> MapLeftOnState<Sin>(Func<Sin, Si> f)
{
return DimapOnState<Sin, So>(f, so => so);
}

/// <summary>
/// Right map on S/State parameter - Covariant
/// </summary>
/// <param name="f"></param>
/// <typeparam name="Son"></typeparam>
/// <returns></returns>
public InternalDecider<C, Si, Son, Ei, Eo> MapOnState<Son>(Func<So, Son> f)
{
return DimapOnState<Si, Son>(si => si, f);
}

/// <summary>
/// Apply on S/State - Applicative
/// </summary>
/// <param name="ff"></param>
/// <typeparam name="Son"></typeparam>
/// <returns></returns>
public InternalDecider<C, Si, Son, Ei, Eo> ApplyOnState<Son>(InternalDecider<C, Si, Func<So, Son>, Ei, Eo> ff)
{
return new InternalDecider<C, Si, Son, Ei, Eo>(
(c, si) => ff.Decide(c, si).Concat(Decide(c, si)),
(si, ei) => ff.Evolve(si, ei)(Evolve(si, ei)),
ff.InitialState(InitialState)
);
}

/// <summary>
/// Product on S/State parameter - Applicative
/// </summary>
/// <param name="fb"></param>
/// <typeparam name="Son"></typeparam>
/// <returns></returns>
public InternalDecider<C, Si, Tuple<So, Son>, Ei, Eo> ProductOnState<Son>(InternalDecider<C, Si, Son, Ei, Eo> fb)
{
return ApplyOnState(fb.MapOnState(b => new Func<So, Tuple<So, Son>>(a => new Tuple<So, Son>(a, b))));
}
}

internal static class InternalDeciderExtensions
{
/// <summary>
/// Combine [InternalDecider]s into one big [InternalDecider]
///
/// Possible to use when:
/// - [Ei] and [Ei2] have common superclass [Ei_SUPER]
/// - [Eo] and [Eo2] have common superclass [Eo_SUPER]
/// - [C] and [C2] have common superclass [C_SUPER]
/// </summary>
/// <param name="x">First Decider</param>
/// <param name="y">Second Decider</param>
/// <typeparam name="C">Command type of the first Decider</typeparam>
/// <typeparam name="Si">Input_State type of the first Decider</typeparam>
/// <typeparam name="So">Output_State type of the first Decider</typeparam>
/// <typeparam name="Ei">Input_Event type of the first Decider</typeparam>
/// <typeparam name="Eo">Output_Event type of the first Decider</typeparam>
/// <typeparam name="C2">Command type of the second Decider</typeparam>
/// <typeparam name="Si2">Input_State type of the second Decider</typeparam>
/// <typeparam name="So2">Output_State type of the second Decider</typeparam>
/// <typeparam name="Ei2">Input_Event type of the second Decider</typeparam>
/// <typeparam name="Eo2">Output_Event type of the second Decider</typeparam>
/// <typeparam name="C_SUPER">super type of the command types C and C2</typeparam>
/// <typeparam name="Ei_SUPER">Super type of the Ei and Ei2 types</typeparam>
/// <typeparam name="Eo_SUPER">super type of the Eo and Eo2 types</typeparam>
/// <returns></returns>
public static InternalDecider<C_SUPER?, Tuple<Si, Si2>, Tuple<So, So2>, Ei_SUPER, Eo_SUPER?> Combine<C, Si, So, Ei,
Eo, C2, Si2, So2, Ei2, Eo2, C_SUPER, Ei_SUPER, Eo_SUPER>(
this InternalDecider<C?, Si, So, Ei?, Eo?> x,
InternalDecider<C2?, Si2, So2, Ei2?, Eo2?> y)
where C : class?, C_SUPER?
where C2 : class?, C_SUPER
where Ei : Ei_SUPER
where Eo : Eo_SUPER
where Ei2 : Ei_SUPER
where Eo2 : Eo_SUPER
{
var deciderX = x.MapLeftOnCommand<C_SUPER?>(c => (c as C)!)
.MapLeftOnState<Tuple<Si, Si2>>(pair => pair.Item1)
.DimapOnEvent<Ei_SUPER, Eo_SUPER?>(ei => (Ei)ei!, eo => eo);

var deciderY = y.MapLeftOnCommand<C_SUPER?>(c => (c as C2)!)
.MapLeftOnState<Tuple<Si, Si2>>(pair => pair.Item2)
.DimapOnEvent<Ei_SUPER, Eo_SUPER?>(ei => (Ei2)ei!, eo => eo);

return deciderX.ProductOnState(deciderY);
}

internal static Decider<C, S, E> AsDecider<C, S, E>(this InternalDecider<C, S, S, E, E> internalDecider)
{
return new Decider<C, S, E>(internalDecider.Decide, internalDecider.Evolve, internalDecider.InitialState);
}
}

0 comments on commit 7cd433a

Please sign in to comment.