Last active
November 7, 2018 06:36
-
-
Save mat3u/8091ba6e4e93ef2456d3d25319d1b236 to your computer and use it in GitHub Desktop.
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 System.Collections.Generic; | |
using System.Linq; | |
namespace code | |
{ | |
using static TryExtensions; | |
using StringMapper = Func<string, Maybe<string>>; | |
#region Marine Domain Types | |
public class Duty | |
{ | |
public string Id { get; set; } | |
public override string ToString() | |
{ | |
return Id; | |
} | |
} | |
public class MusterList | |
{ | |
public string Id { get; set; } | |
public string DutyId { get; set; } | |
public override string ToString() | |
{ | |
return $"{Id}: {DutyId}"; | |
} | |
} | |
public class Cabin | |
{ | |
public int Id { get; set; } | |
public IEnumerable<string> Seats { get; set; } | |
public Cabin(int id) | |
{ | |
Id = id; | |
Seats = Enumerable.Range(1, 4).Select(x => $"{id}-{x}"); | |
} | |
public override string ToString() | |
{ | |
return $"{Id}"; | |
} | |
} | |
public class CrewMember | |
{ | |
public string Id { get; set; } | |
public string MusterListId { get; set; } | |
public Maybe<string> MaybeMusterListId => MusterListId.AsMaybe(); | |
public IEnumerable<Cabin> Cabins { get; set; } | |
public override string ToString() | |
{ | |
return $"{Id}: {MusterListId}"; | |
} | |
} | |
public class Ship | |
{ | |
public IEnumerable<CrewMember> Crew => new List<CrewMember>() { | |
new CrewMember { Id = "1", Cabins = new List<Cabin>(){new Cabin(1), new Cabin(2)} }, | |
new CrewMember { Id = "2", Cabins = new List<Cabin>(){new Cabin(3), new Cabin(5)} }, | |
new CrewMember { Id = "3", Cabins = new List<Cabin>(){new Cabin(4), new Cabin(6)} } | |
}; | |
} | |
#endregion | |
class Program | |
{ | |
const string crewMemberId = "existing"; | |
public static void BadExample() | |
{ | |
var bad = new BadService(); | |
Duty duty = null; | |
var cm = bad.FindCrewMember(crewMemberId); | |
if (null != cm?.MusterListId) | |
{ | |
var musterList = bad.FindMusterList(cm.MusterListId); | |
if (null != musterList) | |
{ | |
duty = bad.FindDuty(musterList.DutyId); | |
} | |
} | |
Console.WriteLine(duty); | |
} | |
public static void UglyExample() | |
{ | |
var better = new BetterService(); | |
var duty = better.FindCrewMember(crewMemberId) | |
.Bind(cm => cm.MaybeMusterListId) | |
.Bind(id => better.FindMusterList(id)) | |
.Bind(ml => better.FindDuty(ml.DutyId)); | |
duty.Match( | |
Some: v => Console.WriteLine("Found: {0}", v), | |
None: () => Console.WriteLine("Not Found!") | |
); | |
} | |
public static void GoodExample() | |
{ | |
var better = new BetterService(); | |
var duty = from cm in better.FindCrewMember(crewMemberId) | |
from id in cm.MaybeMusterListId | |
from ml in better.FindMusterList(id) | |
from dt in better.FindDuty(ml.DutyId) | |
select dt; | |
duty.Match( | |
Some: v => Console.WriteLine("Found: {0}", v), | |
None: () => Console.WriteLine("Not Found!") | |
); | |
} | |
public static void GeneralizationOfMaybe() | |
{ | |
var ship = new Ship(); | |
var cabins = ship.Crew | |
.SelectMany(cm => cm.Cabins) | |
.SelectMany(cb => cb.Seats); | |
Console.WriteLine("Seats available in cabins: \r\n {0}!", String.Join(",\r\n", cabins.Select(x => $"\t{x}"))); | |
} | |
public static void Railway() | |
{ | |
var value = 7; | |
Validators.IsAboveZero(value) | |
.Bind(Validators.IsOdd) | |
.Bind(Validators.IsPrime) | |
.Match( | |
Left: v => Console.WriteLine(v), | |
Right: e => Console.WriteLine("Validation message: {0}", e) | |
); | |
} | |
public static void ErrorHandling() { | |
var svc = new BetterService(); | |
var dutyId = "MusterMaster"; | |
Try(() => { | |
return svc.AssignDutyToCrewMember("cm1", dutyId); | |
}) | |
.Bind(id => svc.RevokeCrewMemberDuty(id, dutyId)) | |
.Match( | |
success: id => Console.WriteLine("Duty has been reassigned from cm1 to {0}", id), | |
failure: e => Console.Error.WriteLine("An error has occured: {0}", e.Message) | |
); | |
} | |
static void Main(string[] args) | |
{ | |
// 1. Maybe | |
BadExample(); | |
// UglyExample(); | |
// GoodExample(); | |
// 2. Something more generic than Maybe | |
// GeneralizationOfMaybe(); | |
// 3. Railway | |
// Railway(); | |
// 4. Try | |
// ErrorHandling(); | |
// 5.Monad Laws | |
// // Left Identity - Return is effect-free | |
// var ma = Maybe.Some("abc"); | |
// StringMapper fa = s => Maybe.Some(s + s); | |
// Console.Write("Left Identity = "); | |
// Console.WriteLine(ma.Bind(fa) == fa("abc")); | |
// // Right Identity - Bind is effect-free | |
// var mb = Maybe.Some("abc"); | |
// StringMapper fb = s => Maybe.Some(s); | |
// Console.Write("Right Identity = "); | |
// Console.WriteLine(mb.Bind(fb) == mb); | |
// // Associativity | |
// Console.Write("Associativity = "); | |
// Console.WriteLine(ma.Bind(fa).Bind(fa) == ma.Bind(pa => fa(pa).Bind(pb => fa(pb)))); | |
} | |
} | |
#region Railway | |
public class Either<L, R> | |
{ | |
public L Left { get; } | |
public R Right { get; } | |
public bool IsLeft { get; } | |
public Either(L left, R right, bool isLeft) | |
{ | |
this.Left = left; | |
this.Right = right; | |
this.IsLeft = isLeft; | |
} | |
public Either<L, R> Bind(Func<L, Either<L, R>> func) | |
{ | |
if (!IsLeft) | |
{ | |
return this; | |
} | |
return func(this.Left); | |
} | |
public void Match(Action<L> Left, Action<R> Right) | |
{ | |
if (IsLeft) | |
{ | |
Left(this.Left); | |
return; | |
} | |
Right(this.Right); | |
} | |
} | |
public static class Either | |
{ | |
public static Either<L, R> Left<L, R>(L left) | |
{ | |
return new Either<L, R>(left, default(R), true); | |
} | |
public static Either<L, R> Right<L, R>(R right) | |
{ | |
return new Either<L, R>(default(L), right, false); | |
} | |
} | |
public static class Validators | |
{ | |
#region Nothing interesting | |
public static Either<int, string> Failure(string msg) | |
{ | |
return Either.Right<int, string>(msg); | |
} | |
public static Either<int, string> OK(int value) | |
{ | |
return Either.Left<int, string>(value); | |
} | |
#endregion | |
public static Either<int, string> IsAboveZero(int value) | |
{ | |
if (value <= 0) | |
{ | |
return Failure("Value is bellow or equal to zero!"); | |
} | |
return OK(value); | |
} | |
public static Either<int, string> IsOdd(int value) | |
{ | |
if (value % 2 == 0) | |
{ | |
return Failure("Value is odd!"); | |
} | |
return OK(value); | |
} | |
public static Either<int, string> IsPrime(int value) | |
{ | |
var primes = new List<int>() { 2, 3, 5, 7, 11, 13 }; | |
if (primes.Contains(value)) | |
{ | |
return OK(value); | |
} | |
return Failure("Value is not prime (sorta)!"); | |
} | |
} | |
#endregion | |
#region Maybe | |
public static class Maybe | |
{ | |
public static Maybe<T> None<T>() | |
{ | |
return new Maybe<T>(); | |
} | |
public static Maybe<T> Some<T>(T value) | |
{ | |
if (null == value) | |
{ | |
return new Maybe<T>(); | |
} | |
return new Maybe<T>(value); | |
} | |
} | |
public class Maybe<T> | |
{ | |
private readonly T value; | |
public bool HasValue { get; } | |
public Maybe(T value) | |
{ | |
this.value = value; | |
this.HasValue = true; | |
} | |
public Maybe() | |
{ | |
this.HasValue = false; | |
} | |
public Maybe<U> Bind<U>(Func<T, Maybe<U>> f) | |
{ | |
if (HasValue) | |
{ | |
return f(value); | |
} | |
return Maybe.None<U>(); | |
} | |
public T Get() | |
{ | |
if (!HasValue) throw new InvalidOperationException(); | |
return value; | |
} | |
#region Equality | |
public override int GetHashCode() | |
{ | |
return HasValue ? value.GetHashCode() : 0; | |
} | |
public override bool Equals(object obj) | |
{ | |
if (null == obj) return false; | |
if (!(obj is Maybe<T>)) return false; | |
var other = (Maybe<T>)obj; | |
if (HasValue && other.HasValue) | |
{ | |
return other.value.Equals(this.value); | |
} | |
if (!HasValue && !other.HasValue) | |
{ | |
return true; | |
} | |
return false; | |
} | |
public static bool operator ==(Maybe<T> self, Maybe<T> other) | |
{ | |
return other.Equals(self); | |
} | |
public static bool operator !=(Maybe<T> self, Maybe<T> other) | |
{ | |
return !other.Equals(self); | |
} | |
#endregion | |
} | |
public static class MaybeExtensions | |
{ | |
// Enable LINQ | |
public static Maybe<U> SelectMany<S, T, U>(this Maybe<S> source, Func<S, Maybe<T>> collectionSelector, Func<S, T, U> projection) | |
{ | |
return source.Bind(v => collectionSelector(v).Bind(c => Maybe.Some(projection(v, c)))); | |
} | |
// Other naming | |
public static Maybe<U> FlatMap<T, U>(this Maybe<T> source, Func<T, Maybe<U>> projection) | |
{ | |
if (null == source) | |
{ | |
return Maybe.None<U>(); | |
} | |
return source.Bind(projection); | |
} | |
// Allow Matching | |
public static U Match<T, U>(this Maybe<T> source, Func<T, U> Some, Func<U> None) | |
{ | |
if (null != source && source.HasValue) | |
{ | |
return Some(source.Get()); | |
} | |
return None(); | |
} | |
public static void Match<T>(this Maybe<T> source, Action<T> Some, Action None) | |
{ | |
if (null != source && source.HasValue) | |
{ | |
Some(source.Get()); | |
} | |
else | |
{ | |
None(); | |
} | |
} | |
// Help construct Maybe from existing code | |
public static Maybe<T> AsMaybe<T>(this T source) | |
where T : class | |
{ | |
if (null == source) | |
{ | |
return Maybe.None<T>(); | |
} | |
return Maybe.Some(source); | |
} | |
} | |
public class BadService | |
{ | |
#region Nothing Interesting | |
public MusterList FindMusterList(string id) | |
{ | |
if ("existing" != id) | |
{ | |
return null; | |
} | |
return new MusterList { Id = "existing", DutyId = "existing" }; | |
} | |
public Duty FindDuty(string id) | |
{ | |
if ("existing" != id) | |
{ | |
return null; | |
} | |
return new Duty { Id = "existing" }; | |
} | |
public CrewMember FindCrewMember(string id) | |
{ | |
if ("existing" != id) | |
{ | |
return null; | |
} | |
return new CrewMember { Id = "existing", MusterListId = "existing" }; | |
} | |
#endregion | |
} | |
public class BetterService | |
{ | |
#region Nothing Interesting | |
public Maybe<MusterList> FindMusterList(string id) | |
{ | |
if ("existing" != id) | |
{ | |
return Maybe.None<MusterList>(); | |
} | |
return Maybe.Some(new MusterList { Id = "existing", DutyId = "existing" }); | |
} | |
public Maybe<Duty> FindDuty(string id) | |
{ | |
if ("existing" != id) | |
{ | |
return Maybe.None<Duty>(); | |
} | |
return Maybe.Some(new Duty { Id = "existing" }); | |
} | |
public Maybe<CrewMember> FindCrewMember(string id) | |
{ | |
if ("existing" != id) | |
{ | |
return Maybe.None<CrewMember>(); | |
} | |
return Maybe.Some(new CrewMember { Id = "existing", MusterListId = "existing" }); | |
} | |
public string AssignDutyToCrewMember(string crewMemberId, string dutyId) | |
{ | |
throw new InvalidOperationException("Unauthorized operation!"); | |
} | |
public Try<string> RevokeCrewMemberDuty(string crewMemberId, string dutyId) { | |
return new Try<string>(crewMemberId); | |
} | |
#endregion | |
} | |
#endregion | |
#region Try | |
public class Try<T> | |
{ | |
private T value; | |
public Exception Exception = null; | |
public bool IsSuccess => null == Exception; | |
public bool IsFailure => null != Exception; | |
public Try(T v) => value = v; | |
public Try(Exception e) => Exception = e; | |
public Try<U> Bind<U>(Func<T, Try<U>> func) | |
{ | |
if (IsFailure) | |
{ | |
return new Try<U>(Exception); | |
} | |
return func(value); | |
} | |
public Try<T> Recover<E>(Func<E, Try<T>> recover) where E: Exception { | |
if(Exception is E) { | |
return recover(Exception as E); | |
} | |
return this; | |
} | |
public T Get() | |
{ | |
if (IsFailure) throw Exception; // TODO: Preserve stack | |
return value; | |
} | |
} | |
public static class TryExtensions | |
{ | |
public static Try<U> Try<U>(Func<U> op) | |
{ | |
try | |
{ | |
return new Try<U>(op()); | |
} | |
catch (Exception e) | |
{ | |
return new Try<U>(e); | |
} | |
} | |
public static void Match<T>(this Try<T> source, Action<T> success, Action<Exception> failure) { | |
if(source.IsSuccess) { | |
success(source.Get()); | |
} else { | |
failure(source.Exception); | |
} | |
} | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment