Skip to content

Instantly share code, notes, and snippets.

@pizycki
Created October 15, 2018 20:21
Show Gist options
  • Save pizycki/0d508f1883d21be5a6da72a511309e19 to your computer and use it in GitHub Desktop.
Save pizycki/0d508f1883d21be5a6da72a511309e19 to your computer and use it in GitHub Desktop.
Payments Free
using System;
using LanguageExt;
using static LanguageExt.Prelude;
// Based on https://github.com/louthy/language-ext/tree/master/Samples/BankingAppSample
namespace FreeIzzy
{
public class StartPaymentRequest : Record<StartPaymentRequest>
{
public StartPaymentRequest(decimal amount)
{
Amount = amount;
}
public decimal Amount { get; }
}
public class TransactionId : Record<TransactionId>
{
public TransactionId(Guid value)
{
Value = value;
}
public Guid Value { get; }
}
public abstract class FreePayment<A>
{
/// <summary>
/// Identity type - simply returns the value provided
/// </summary>
public class Return : FreePayment<A>
{
public A Value { get; }
public Return(A value) =>
Value = value;
}
/// <summary>
/// Represents request validation
/// </summary>
public class ValidateRequest : FreePayment<A>
{
public StartPaymentRequest Request { get; }
public Func<FreePayment<A>> Next { get; } // We have nothing to pass further if validation passes
public ValidateRequest(StartPaymentRequest request, Func<FreePayment<A>> next)
{
Request = request;
Next = next;
}
}
/// <summary>
/// Represents payment start operation
/// </summary>
public class StartPayment : FreePayment<A>
{
public StartPaymentRequest Request { get; }
public Func<TransactionId, FreePayment<A>> Next { get; }
public StartPayment(StartPaymentRequest request, Func<TransactionId, FreePayment<A>> next)
{
Request = request;
Next = next;
}
}
}
public static class FreePaymentExtensions
{
/// <summary>
/// Binds operations.
/// Used by LINQ
/// </summary>
[FunSignature("MA -> (A -> MB) -> MB")]
public static FreePayment<B> Bind<A, B>(this FreePayment<A> m, Func<A, FreePayment<B>> f)
{
if (m is FreePayment<A>.Return rt) return f(rt.Value);
else if (m is FreePayment<A>.ValidateRequest vr) return new FreePayment<B>.ValidateRequest(vr.Request, () => vr.Next().Bind(f));
else if (m is FreePayment<A>.StartPayment sp) return new FreePayment<B>.StartPayment(sp.Request, id => sp.Next(id).Bind(f));
else throw new NotSupportedException();
// These are statements. In FP we prefer using expressions (?:)
// These can be easily change into expression.
}
[FunSignature("MA -> (A -> B) -> MB")]
public static FreePayment<B> Select<A, B>(this FreePayment<A> ma, Func<A, B> f)
{
return ma.Bind(a =>
{
B b = f(a);
FreePayment<B> mb = new FreePayment<B>.Return(b);
return mb;
});
}
[FunSignature("MA -> (A -> MB) -> (A -> B -> C) -> MC")]
public static FreePayment<C> SelectMany<A, B, C>(this FreePayment<A> ma, Func<A, FreePayment<B>> bind, Func<A, B, C> projection)
{
return ma.Bind(a => bind(a).Select(b => projection(a, b)));
}
}
public class Error : Record<Error>
{
public Error(string value)
{
Value = value;
}
public string Value { get; }
}
// TODO
public static class FreePayment
{
}
public class Env
{
}
public static class Interpreter
{
public static Either<Error, (A, Env)> Interpret<A>(FreePayment<A> dsl, Env env) =>
dsl is FreePayment<A>.Return r ? Right<Error, (A, Env)>((r.Value, env))
: dsl is FreePayment<A>.ValidateRequest vr ? ValidateRequest(vr, env)
: dsl is FreePayment<A>.StartPayment sp ? StartPayment(sp, env)
: throw new NotSupportedException();
public static Either<Error, (A, Env)> ValidateRequest<A>(FreePayment<A>.ValidateRequest requestValidation, Env env)
{
var request = requestValidation.Request;
return request.Amount <= 0 ? Left(new Error("Amount must be positive value."))
: Interpret(requestValidation.Next(), env);
}
public static Either<Error, (A, Env)> StartPayment<A>(FreePayment<A>.StartPayment paymentStart, Env env)
{
// TODO do some IO operations
var transId = new TransactionId(Guid.NewGuid());
return Interpret(paymentStart.Next(transId), env);
}
}
public class FunSignatureAttribute : Attribute
{
public FunSignatureAttribute(string value)
{
Value = value;
}
public string Value { get; }
}
public class App
{
public Either<Error, TransactionId> Run()
{
var env = new Env();
var request = new StartPaymentRequest(42);
var dsl = from _1 in new FreePayment<Unit>.ValidateRequest(request, () => new FreePayment<Unit>.Return(Unit.Default))
from transId in new FreePayment<TransactionId>.StartPayment(request, id => new FreePayment<TransactionId>.Return(id))
select transId;
var startedPaymentTransactionId = Interpreter.Interpret(dsl, env);
return startedPaymentTransactionId.Match(
Right: r => Right<Error, TransactionId>(r.Item1),
Left: l => Left<Error, TransactionId>(l));
}
}
class Program
{
static void Main(string[] args)
{
new App().Run().Match(
Right: id => Console.WriteLine($"Payment has been started! Transaction ID = {id.Value}"),
Left: err => Console.WriteLine($"Given error has occured: {err.Value}"));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment