Last active
January 14, 2018 18:31
-
-
Save dadhi/026350db603dd348af6c0369a9a5bd0b to your computer and use it in GitHub Desktop.
Pure C# IO monad (original is using LanguageExt lib https://gist.github.com/louthy/524fbe8965d3a2aae1b576cdd8e971e4)
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
// Modified from the original https://gist.github.com/louthy/524fbe8965d3a2aae1b576cdd8e971e4 | |
using System; | |
using System.IO; | |
using System.Linq; | |
using System.Collections.Generic; | |
using System.Threading.Tasks; | |
using static System.Linq.Enumerable; | |
using static IOExample.Unit; | |
namespace IOExample | |
{ | |
public sealed class Unit | |
{ | |
public static readonly Unit unit = new Unit(); | |
private Unit() {} | |
} | |
public static class EnumerableExt | |
{ | |
public static IEnumerable<C> zip<A, B, C>( | |
this IEnumerable<A> aa, IEnumerable<B> bb, | |
Func<A, B, C> zip) | |
{ | |
var ae = aa.GetEnumerator(); | |
var be = bb.GetEnumerator(); | |
while (ae.MoveNext() && be.MoveNext()) | |
yield return zip(ae.Current, be.Current); | |
} | |
} | |
public static class TestIO | |
{ | |
static IO<Unit> TotallyPureIOComputation(string path) => | |
from lines in IO.ReadAllLines(path) | |
from _1 in IO.Log($"There are {lines.Count()} lines") | |
from _2 in IO.Log($"Prepending line numbers") | |
let newLines = Range(1, Int32.MaxValue) | |
.Zip(lines, (i, line) => $"{i} {line}") | |
from _3 in IO.WriteAllLines(path, newLines) | |
from _4 in IO.Log($"Lines prepended and file saved successfully") | |
select unit; | |
public static void Test(string path) | |
{ | |
var computation = TotallyPureIOComputation(path); | |
var mockedResult = MockInterpreter.Interpet(computation); | |
var liveResult = LiveInterpreter.Interpet(computation); | |
var liveAsyncResult = LiveInterpreterAsync.Interpet(computation).Result; | |
} | |
} | |
public static class LiveInterpreter | |
{ | |
public static A Interpet<A>(IO<A> ma) => | |
ma is IO<A>.Return r ? r.Value | |
: ma is IO<A>.Faulted e ? throw new Exception(e.Message) | |
: ma is IO<A>.ReadAllLines ra ? Interpet(ra.Next(File.ReadAllLines(ra.Path))) | |
: ma is IO<A>.WriteAllLines wa ? Interpet(wa.Next(WriteAllLines(wa.Path, wa.Output))) | |
: ma is IO<A>.Log log ? Interpet(log.Next(Log(log.Output))) | |
: throw new NotSupportedException(); | |
static Unit Log(string output) | |
{ | |
Console.WriteLine(output); | |
return unit; | |
} | |
static Unit WriteAllLines(string path, IEnumerable<string> output) | |
{ | |
File.WriteAllLines(path, output.ToArray()); | |
return unit; | |
} | |
} | |
public static class LiveInterpreterAsync | |
{ | |
public static async Task<A> Interpet<A>(IO<A> ma) => | |
ma is IO<A>.Return r ? r.Value | |
: ma is IO<A>.Faulted e ? throw new Exception(e.Message) | |
: ma is IO<A>.ReadAllLines ra ? await Interpet(ra.Next(await ReadAllLines(ra.Path))) | |
: ma is IO<A>.WriteAllLines wa ? await Interpet(wa.Next(await WriteAllLines(wa.Path, wa.Output))) | |
: ma is IO<A>.Log log ? await Interpet(log.Next(Log(log.Output))) | |
: throw new NotSupportedException(); | |
static Unit Log(string output) | |
{ | |
Console.WriteLine(output); | |
return unit; | |
} | |
static Task<Unit> WriteAllLines(string path, IEnumerable<string> output) => Task.Run(() => | |
{ | |
File.WriteAllLines(path, output.ToArray()); | |
return unit; | |
}); | |
static Task<IEnumerable<string>> ReadAllLines(string path) => | |
Task.Run(() => (IEnumerable<string>)File.ReadAllLines(path)); | |
} | |
public static class MockInterpreter | |
{ | |
public static A Interpet<A>(IO<A> ma) => | |
ma is IO<A>.Return r ? r.Value | |
: ma is IO<A>.Faulted e ? throw new Exception(e.Message) | |
: ma is IO<A>.ReadAllLines ra ? Interpet(ra.Next(MockReadAllLines(ra.Path))) | |
: ma is IO<A>.WriteAllLines wa ? Interpet(wa.Next(WriteAllLines(wa.Path, wa.Output))) | |
: ma is IO<A>.Log log ? Interpet(log.Next(Log(log.Output))) | |
: throw new NotSupportedException(); | |
static IEnumerable<string> MockReadAllLines(string path) => | |
new string[] {"Hello", "World"}; | |
static Unit Log(string output) | |
{ | |
Console.WriteLine(output); | |
return unit; | |
} | |
static Unit WriteAllLines(string path, IEnumerable<string> output) => unit; | |
} | |
public static class IO | |
{ | |
public static IO<A> Return<A>(A value) => new IO<A>.Return(value); | |
public static IO<Unit> Faulted(string error) => new IO<Unit>.Faulted(error, Return); | |
public static IO<IEnumerable<string>> ReadAllLines(string path) => new IO<IEnumerable<string>>.ReadAllLines(path, Return); | |
public static IO<Unit> WriteAllLines(string path, IEnumerable<string> output) => new IO<Unit>.WriteAllLines(path, output, Return); | |
public static IO<Unit> Log(string output) => new IO<Unit>.Log(output, Return); | |
} | |
public static class IOExt | |
{ | |
public static IO<B> SelectMany<A, B>(this IO<A> ma, Func<A, IO<B>> f) => | |
ma is IO<A>.Return r ? f(r.Value) | |
: ma is IO<A>.Faulted e ? new IO<B>.Faulted(e.Message, n => e.Next(n).SelectMany(f)) | |
: ma is IO<A>.ReadAllLines ra ? new IO<B>.ReadAllLines(ra.Path, n => ra.Next(n).SelectMany(f)) | |
: ma is IO<A>.WriteAllLines wa ? new IO<B>.WriteAllLines(wa.Path, wa.Output, n => wa.Next(n).SelectMany(f)) | |
: ma is IO<A>.Log log ? new IO<B>.Log(log.Output, n => log.Next(n).SelectMany(f)) as IO<B> | |
: throw new NotSupportedException(); | |
public static IO<B> Select<A, B>(this IO<A> ma, Func<A, B> f) => | |
ma.SelectMany(a => IO.Return(f(a))); | |
public static IO<C> SelectMany<A, B, C>(this IO<A> ma, Func<A, IO<B>> bind, Func<A, B, C> project) => | |
ma.SelectMany(a => bind(a).Select(b => project(a, b))); | |
} | |
public abstract class IO<A> | |
{ | |
public class Return : IO<A> | |
{ | |
public readonly A Value; | |
public Return(A value) => | |
Value = value; | |
} | |
public class Faulted : IO<A> | |
{ | |
public readonly string Message; | |
public readonly Func<Unit, IO<A>> Next; | |
public Faulted(string message, Func<Unit, IO<A>> next) => | |
(Message, Next) = (message, next); | |
} | |
public class ReadAllLines : IO<A> | |
{ | |
public readonly string Path; | |
public readonly Func<IEnumerable<string>, IO<A>> Next; | |
public ReadAllLines(string path, Func<IEnumerable<string>, IO<A>> next) => | |
(Path, Next) = (path, next); | |
} | |
public class WriteAllLines : IO<A> | |
{ | |
public readonly string Path; | |
public readonly IEnumerable<string> Output; | |
public readonly Func<Unit, IO<A>> Next; | |
public WriteAllLines(string path, IEnumerable<string> output, Func<Unit, IO<A>> next) => | |
(Path, Output, Next) = (path, output, next); | |
} | |
public class Log : IO<A> | |
{ | |
public readonly string Output; | |
public readonly Func<Unit, IO<A>> Next; | |
public Log(string output, Func<Unit, IO<A>> next) => | |
(Output, Next) = (output, next); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment