Created
October 15, 2018 20:21
-
-
Save pizycki/0d508f1883d21be5a6da72a511309e19 to your computer and use it in GitHub Desktop.
Payments Free
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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