Skip to content

Instantly share code, notes, and snippets.

@mat3u
Last active November 7, 2018 06:36
Show Gist options
  • Save mat3u/8091ba6e4e93ef2456d3d25319d1b236 to your computer and use it in GitHub Desktop.
Save mat3u/8091ba6e4e93ef2456d3d25319d1b236 to your computer and use it in GitHub Desktop.
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