Pure functional, type-safe, composable asynchronous and concurrent programming for .net
An Effect system is an api that allows you to represent side-effects at type level, without actually performing them, so that you can model and compose side-effect-full processes using just pure functions.
dotnet add package NBB.Core.Effects
var eff = Effect.Pure(5)
var eff = Effect.From(System.Guid.NewGuid);
static class ConsoleEffects
{
internal class WriteLine
{
internal record SideEffect(string Value) : ISideEffect;
internal class Handler : ISideEffectHandler<SideEffect, Unit>
{
public Task<Unit> Handle(SideEffect sideEffect, CancellationToken cancellationToken = new CancellationToken())
{
System.Console.WriteLine(sideEffect.Value);
return Unit.Task;
}
}
}
internal class ReadLine
{
internal record SideEffect : ISideEffect<string>;
internal static string Handle(SideEffect _) => System.Console.ReadLine();
}
}
public static class Console
{
public static Effect<Unit> WriteLine(string value) =>
Effect.Of<ConsoleEffects.WriteLine.SideEffect, Unit>(new(value));
public static Effect<string> ReadLine = Effect.Of<ConsoleEffects.ReadLine.SideEffect, string>(new());
}
public static class DependencyInjectionExtensions
{
public static IServiceCollection AddConsoleEffects(this IServiceCollection services)
{
services
.AddSingleton<ISideEffectHandler<ConsoleEffects.WriteLine.SideEffect, Unit>,
ConsoleEffects.WriteLine.Handler>()
.AddSideEffectHandler<ConsoleEffects.ReadLine.SideEffect, string>(ConsoleEffects.ReadLine.Handle);
return services;
}
}
Effect composition is based on the functor, applicative and monad algebras
You can map/bind the value inside an effect using Then.
public static readonly Effect<Unit> Main =
Console.WriteLine("What is your name?")
.Then(Console.ReadLine)
.Then(name => Guid.NewGuid.Then(userId => Greet(name, userId)));
Sequence produces an effect from a sequence of effects, when interpreted, it will interpret the list of effects one after the other.
public static readonly Effect<Unit> Main =
Effect.Sequence(new List<Effect<Unit>>
{
Console.WriteLine("Hello!"),
Console.WriteLine("Good bye!"),
});
Produces a compound effect, that when interpreted, it will concurrently interpret the two composed effects
public static readonly Effect<Unit> Main =
Effect.Parallel(
Console.WriteLine("Hello!"),
Console.WriteLine("Hello2!"));
public static readonly Effect<Unit> Main =
from id in Guid.NewGuid
from name in GetNameById(id)
let name1 = name.ToUpper()
from _ in Console.WriteLine(name1)
select Unit.Value;
Usually effect interpretation is done once in the program entry-point.
using NBB.Core.Effects;
await using var interpreter = Interpreter.CreateDefault(services => services.AddConsoleEffects());
await interpreter.Interpret(Program.Main);