Skip to content

Commit

Permalink
View implemented.
Browse files Browse the repository at this point in the history
  • Loading branch information
zsrdjan committed May 19, 2024
1 parent 96048c2 commit c4c9623
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 68 deletions.
12 changes: 12 additions & 0 deletions src/Fraktalio.FModel.Contracts/IView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Fraktalio.FModel.Contracts;

/// <summary>
/// View interface
/// </summary>
/// <typeparam name="S"></typeparam>
/// <typeparam name="E"></typeparam>
public interface IView<S, in E>
{
Func<S, E, S> Evolve { get; }
S InitialState { get; }
}
82 changes: 82 additions & 0 deletions src/Fraktalio.FModel/InternalView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace Fraktalio.FModel;

/// <summary>
/// [InternalView] is a datatype that represents the event handling algorithm,
/// responsible for translating the events into denormalized state,
/// which is more adequate for querying.
///
/// It has three generic parameters [Si], [So], [E], representing the type of the values that [InternalView] may contain or use.
/// [InternalView] can be specialized for any type of [Si], [So], [E] because these types does not affect its behavior.
/// [InternalView] behaves the same for [E]=[Int] or [E]=YourCustomType, for example.
///
/// [InternalView] is a pure domain component
/// </summary>
/// <param name="evolve">A pure function/lambda that takes input state of type [Si] and input event of type [E] as parameters, and returns the output/new state [So]</param>
/// <param name="initialState">A starting point / An initial state of type [So]</param>
/// <typeparam name="Si">Input State type</typeparam>
/// <typeparam name="So">Output State type</typeparam>
/// <typeparam name="E">Event type</typeparam>
internal class InternalView<Si, So, E>(Func<Si, E, So> evolve, So initialState)
{
internal Func<Si, E, So> Evolve { get; } = evolve;
internal So InitialState { get; } = initialState;

/// <summary>
/// Left map on E/Event parameter - Contravariant
/// </summary>
/// <param name="f">Map function</param>
/// <typeparam name="En">En Event new</typeparam>
/// <returns>Mapped view</returns>
internal InternalView<Si, So, En> MapLeftOnEvent<En>(Func<En, E> f) =>
new(
(si, en) => Evolve(si, f(en)),
InitialState
);

/// <summary>
/// Dimap on S/State parameter - Contravariant on the Si (input State) - Covariant on the So (output State) = Profunctor
/// </summary>
/// <param name="fl"></param>
/// <param name="fr"></param>
/// <typeparam name="Sin">Sin State input new</typeparam>
/// <typeparam name="Son">Son State output new</typeparam>
/// <returns></returns>
public InternalView<Sin, Son, E> DimapOnState<Sin, Son>(Func<Sin, Si> fl, Func<So, Son> fr) =>
new(
(sin, e) => fr(Evolve(fl(sin), e)),
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 InternalView<Sin, So, E> MapLeftOnState<Sin>(Func<Sin, Si> f) => DimapOnState(f, so => so);

/// <summary>
/// Right map on S/State parameter - Covariant
/// </summary>
/// <param name="f"></param>
/// <typeparam name="Son"></typeparam>
/// <returns></returns>
public InternalView<Si, Son, E> MapOnState<Son>(Func<So, Son> f) => DimapOnState<Si, Son>(si => si, f);

/// <summary>
/// Apply on S/State parameter - Applicative
/// </summary>
/// <param name="ff"></param>
/// <typeparam name="Son">Son State output new type</typeparam>
/// <returns></returns>
public InternalView<Si, Son, E> ApplyOnState<Son>(InternalView<Si, Func<So, Son>, E> ff) =>
new(
(si, e) => ff.Evolve(si, e)(Evolve(si, e)),
ff.InitialState(InitialState)
);

internal InternalView<Si, Tuple<So, Son>, E> ProductOnState<Son>(InternalView<Si, Son, E> fb) =>
ApplyOnState(fb.MapOnState(b => new Func<So, Tuple<So, Son>>(a => new Tuple<So, Son>(a, b))));


}
30 changes: 30 additions & 0 deletions src/Fraktalio.FModel/InternalViewExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Fraktalio.FModel;

internal static class InternalViewExtensions
{
/// <summary>
/// Combines [InternalView]s into one bigger [InternalView]
/// </summary>
/// <param name="x">first view</param>
/// <param name="y">second view</param>
/// <typeparam name="Si">Si State input of the first View</typeparam>
/// <typeparam name="So">So State output of the first View</typeparam>
/// <typeparam name="E">E Event of the first View</typeparam>
/// <typeparam name="Si2">Si2 State input of the second View</typeparam>
/// <typeparam name="So2">So2 State output of the second View</typeparam>
/// <typeparam name="E2">E2 Event of the second View</typeparam>
/// <typeparam name="E_SUPER">E_SUPER super type for [E] and [E2]</typeparam>
/// <returns>new View of type [InternalView]<[Pair]<[Si], [Si2]>, [Pair]<[So], [So2]>, [E_SUPER]></returns>
internal static InternalView<Tuple<Si, Si2>, Tuple<So, So2>, E_SUPER> Combine<Si, So, E, Si2, So2, E2, E_SUPER>(
this InternalView<Si, So, E?> x, InternalView<Si2, So2, E2?> y)
where E : class, E_SUPER
where E2 : class, E_SUPER
{
var viewX = x.MapLeftOnEvent<E_SUPER>(e => e as E).MapLeftOnState<Tuple<Si, Si2>>(pair => pair.Item1);
var viewY = y.MapLeftOnEvent<E_SUPER>(e => e as E2).MapLeftOnState<Tuple<Si, Si2>>(pair => pair.Item2);

return viewX.ProductOnState(viewY);
}

internal static View<S, E> AsView<S, E>(this InternalView<S, S, E> view) => new(view.Evolve, view.InitialState);
}
67 changes: 67 additions & 0 deletions src/Fraktalio.FModel/View.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Fraktalio.FModel.Contracts;

namespace Fraktalio.FModel;

/// <summary>
/// [View] is a datatype that represents the event handling algorithm,
/// responsible for translating the events into denormalized state,
/// which is more adequate for querying.
///
/// It has two generic parameters `S`, `E`, representing the type of the values that [View] may contain or use.
/// [View] can be specialized for any type of `S`, `E` because these types does not affect its behavior.
/// [View] behaves the same for `E`=[Int] or `E`=`YourCustomType`.
/// </summary>
/// <param name="evolve">evolve A pure 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">initialState A starting point / An initial state of type [S]</param>
/// <typeparam name="S">State type</typeparam>
/// <typeparam name="E">Event type</typeparam>
public class View<S, E>(Func<S, E, S> evolve, S initialState) : IView<S, E>
{
public Func<S, E, S> Evolve { get; } = evolve;
public S InitialState { get; } = initialState;

/// <summary>
/// Left map on E/Event
/// </summary>
/// <param name="f">Function that maps type `En` to `E`</param>
/// <typeparam name="En">En Event new</typeparam>
/// <returns>New View of type [View]<[S], [En]></returns>
public View<S, En> MapLeftOnEvent<En>(Func<En, E> f) =>
new InternalView<S, S, E>(Evolve, InitialState).MapLeftOnEvent(f).AsView();

/// <summary>
/// Di-map on S/State
/// </summary>
/// <param name="fl">Function that maps type `Sn` to `S`</param>
/// <param name="fr">Function that maps type `S` to `Sn`</param>
/// <typeparam name="Sn">Sn State new</typeparam>
/// <returns>New View of type [View]<[Sn], [E]></returns>
public View<Sn, E> DimapOnState<Sn>(Func<Sn, S> fl, Func<S, Sn> fr) =>
new InternalView<S, S, E>(Evolve, InitialState).DimapOnState(fl, fr).AsView();
}

public static class ViewExtensions
{
/// <summary>
/// Combines [View]s into one [View]
///
/// Possible to use when [E] and [E2] have common superclass [E_SUPER]
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <typeparam name="S">State of the first View</typeparam>
/// <typeparam name="E">Event of the first View</typeparam>
/// <typeparam name="S2">State of the second View</typeparam>
/// <typeparam name="E2">Event of the second View</typeparam>
/// <typeparam name="E_SUPER">Super type for [E] and [E2]</typeparam>
/// <returns></returns>
public static View<Tuple<S, S2>, E_SUPER> Combine<S, E, S2, E2, E_SUPER>(this View<S, E?> x, View<S2, E2?> y)
where E : class, E_SUPER
where E2 : class, E_SUPER
{
var internalViewX = new InternalView<S, S, E?>(x.Evolve, x.InitialState);
var internalViewY = new InternalView<S2, S2, E2?>(y.Evolve, y.InitialState);
var combined = internalViewX.Combine<S, S, E, S2, S2, E2, E_SUPER>(internalViewY);
return combined.AsView();
}
}
13 changes: 13 additions & 0 deletions src/Fraktalio.FModel/ViewBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Fraktalio.FModel;

internal class ViewBuilder<S, E>
{
private Func<S, E, S> Evolve { get; set; } = (s, _) => s;
private Func<S> InitialState { get; set; } = () => throw new Exception("Initial State is not initialized");

public void SetEvolve(Func<S, E, S> value) => Evolve = value;

public void SetInitialState(Func<S> value) => InitialState = value;

public View<S, E> Build() => new(Evolve, InitialState());
}
11 changes: 11 additions & 0 deletions src/Fraktalio.FModel/ViewFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Fraktalio.FModel;

internal static class ViewFactory
{
public static View<S, E> CreateView<S, E>(Action<ViewBuilder<S, E>> buildAction)
{
var builder = new ViewBuilder<S, E>();
buildAction(builder);
return builder.Build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class EvenNumberDecider() : Decider<EvenNumberCommand?, EvenNumberState,
initialState: new EvenNumberState(Description.Create("Initial state"), Number.Create(0)),
decide: (c, s) =>
{
if (c != null && c.Value > 1000)
if (c != null && c.Number > 1000)
{
throw new NotSupportedException("Sorry");
}
Expand All @@ -16,25 +16,21 @@ public class EvenNumberDecider() : Decider<EvenNumberCommand?, EvenNumberState,
EvenNumberCommand.AddEvenNumber add =>
[
new EvenNumberAdded(Description.Create(add.Description),
Number.Create((int) s.Value + add.Value))
s.Value + add.Number)
],
EvenNumberCommand.SubtractEvenNumber subtract =>
[
new EvenNumberSubtracted(Description.Create(subtract.Description),
Number.Create((int) s.Value - subtract.Value))
s.Value - subtract.Number)
],
_ => Array.Empty<EvenNumberEvent>()
_ => []
};
},
evolve: (s, e) =>
evolve: (s, e) => e switch
{
return e switch
{
EvenNumberAdded added => new EvenNumberState(
Description.Create(s.Description + added.Description), Number.Create(added.Value)),
EvenNumberSubtracted subtracted => new EvenNumberState(
Description.Create(s.Description - subtracted.Description), Number.Create(subtracted.Value)),
_ => s
};
}
);
EvenNumberAdded added => new EvenNumberState(
s.Description + added.Description, added.Value),
EvenNumberSubtracted subtracted => new EvenNumberState(
s.Description - subtracted.Description, subtracted.Value),
_ => s
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Fraktalio.FModel.Tests.Examples.Numbers.Even;

public class EvenNumberView() : View<EvenNumberState, EvenNumberEvent?>(
(s, e) =>
{
return e switch
{
EvenNumberAdded => new EvenNumberState(s.Description + e.Description,
s.Value + e.Value),
EvenNumberSubtracted => new EvenNumberState(s.Description - e.Description,
s.Value - e.Value),
_ => s
};
},
new EvenNumberState(Description.Create("Initial state"), Number.Create(0)));

This file was deleted.

4 changes: 2 additions & 2 deletions test/Fraktalio.FModel.Tests/Examples/Numbers/Number.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ namespace Fraktalio.FModel.Tests.Examples.Numbers;

public record Number(int Value)
{
public static Description operator +(Number a, Number b) => new($"{a.Value} + {b.Value}");
public static Number operator +(Number a, Number b) => Create(a.Value + b.Value);

public static Description operator -(Number a, Number b) => new($"{a.Value} - {b.Value}");
public static Number operator -(Number a, Number b) => Create(a.Value - b.Value);

public static Number Create(int value) => new(value);

Expand Down
37 changes: 12 additions & 25 deletions test/Fraktalio.FModel.Tests/Examples/Numbers/NumberCommand.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,24 @@
namespace Fraktalio.FModel.Tests.Examples.Numbers;

public abstract class NumberCommand
public abstract class NumberCommand(Description description, Number number)
{
public abstract Description Description { get; }
public abstract Number Value { get; }
public Description Description { get; } = description;
public Number Number { get; } = number;

public abstract class EvenNumberCommand : NumberCommand
public abstract class EvenNumberCommand(Description description, Number number) : NumberCommand(description, number)
{
public sealed class AddEvenNumber(Description description, Number value) : EvenNumberCommand
{
public override Description Description { get; } = description;
public override Number Value { get; } = value;
}
public sealed class AddEvenNumber(Description description, Number number)
: EvenNumberCommand(description, number);

public sealed class SubtractEvenNumber(Description description, Number value) : EvenNumberCommand
{
public override Description Description { get; } = description;
public override Number Value { get; } = value;
}
public sealed class SubtractEvenNumber(Description description, Number number)
: EvenNumberCommand(description, number);
}

public abstract class OddNumberCommand : NumberCommand
public abstract class OddNumberCommand(Description description, Number number) : NumberCommand(description, number)
{
public sealed class AddOddNumber(Description description, Number value) : OddNumberCommand
{
public override Description Description { get; } = description;
public override Number Value { get; } = value;
}
public sealed class AddOddNumber(Description description, Number number) : OddNumberCommand(description, number);

public sealed class SubtractOddNumber(Description description, Number value) : OddNumberCommand
{
public override Description Description { get; } = description;
public override Number Value { get; } = value;
}
public sealed class SubtractOddNumber(Description description, Number number)
: OddNumberCommand(description, number);
}
}
5 changes: 2 additions & 3 deletions test/Fraktalio.FModel.Tests/Examples/Numbers/NumberEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ namespace Fraktalio.FModel.Tests.Examples.Numbers;
public abstract record NumberEvent(Description Description, Number Value);

public abstract record EvenNumberEvent(Description Description, Number Value) : NumberEvent(Description, Value);

public sealed record EvenNumberAdded(Description Description, Number Value) : EvenNumberEvent(Description, Value);

public sealed record EvenNumberSubtracted(Description Description, Number Value) : EvenNumberEvent(Description, Value);


public abstract record OddNumberEvent(Description Description, Number Value) : NumberEvent(Description, Value);

public sealed record OddNumberAdded(Description Description, Number Value) : OddNumberEvent(Description, Value);

public sealed record OddNumberSubtracted(Description Description, Number Value)
: OddNumberEvent(Description, Value);

: OddNumberEvent(Description, Value);
6 changes: 5 additions & 1 deletion test/Fraktalio.FModel.Tests/Examples/Numbers/NumberState.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
namespace Fraktalio.FModel.Tests.Examples.Numbers;

public abstract record NumberState(Description Description, Number Value);
public abstract record NumberState(Description Description, Number Value);

public sealed record OddNumberState(Description Description, Number Value) : NumberState(Description, Value);

public sealed record EvenNumberState(Description Description, Number Value) : NumberState(Description, Value);
Loading

0 comments on commit c4c9623

Please sign in to comment.