Skip to content

Instantly share code, notes, and snippets.

@dadhi
Last active January 14, 2018 18:31
Show Gist options
  • Save dadhi/026350db603dd348af6c0369a9a5bd0b to your computer and use it in GitHub Desktop.
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)
// 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