-
-
Save louthy/524fbe8965d3a2aae1b576cdd8e971e4 to your computer and use it in GitHub Desktop.
// | |
// See https://github.com/louthy/language-ext | |
// | |
using System; | |
using System.IO; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using LanguageExt; | |
using static LanguageExt.Prelude; | |
using static LanguageExt.List; | |
namespace ConsoleApp8 | |
{ | |
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 = zip(Range(1, Int32.MaxValue), lines) | |
.Map((i, line) => $"{i} {line}") | |
.ToSeq() | |
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).ToSeq())) | |
: 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, Seq<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, Seq<string> output) => Task.Run(() => | |
{ | |
File.WriteAllLines(path, output.ToArray()); | |
return unit; | |
}); | |
static Task<Seq<string>> ReadAllLines(string path) => | |
Task.Run(() => File.ReadAllLines(path).ToSeq()); | |
} | |
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 Seq<string> MockReadAllLines(string path) => | |
Seq("Hello", "World"); | |
static Unit Log(string output) | |
{ | |
Console.WriteLine(output); | |
return unit; | |
} | |
static Unit WriteAllLines(string path, Seq<string> output) | |
{ | |
return 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<Seq<string>> ReadAllLines(string path) => new IO<Seq<string>>.ReadAllLines(path, Return); | |
public static IO<Unit> WriteAllLines(string path, Seq<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> Bind<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).Bind(f)) | |
: ma is IO<A>.ReadAllLines ra ? new IO<B>.ReadAllLines(ra.Path, n => ra.Next(n).Bind(f)) | |
: ma is IO<A>.WriteAllLines wa ? new IO<B>.WriteAllLines(wa.Path, wa.Output, n => wa.Next(n).Bind(f)) | |
: ma is IO<A>.Log log ? new IO<B>.Log(log.Output, n => log.Next(n).Bind(f)) as IO<B> | |
: throw new NotSupportedException(); | |
public static IO<B> Select<A, B>(this IO<A> ma, Func<A, B> f) => | |
ma.Bind(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.Bind(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 = message; | |
Next = next; | |
} | |
} | |
public class ReadAllLines : IO<A> | |
{ | |
public readonly string Path; | |
public readonly Func<Seq<string>, IO<A>> Next; | |
public ReadAllLines(string path, Func<Seq<string>, IO<A>> next) | |
{ | |
Path = path; | |
Next = next; | |
} | |
} | |
public class WriteAllLines : IO<A> | |
{ | |
public readonly string Path; | |
public readonly Seq<string> Output; | |
public readonly Func<Unit, IO<A>> Next; | |
public WriteAllLines(string path, Seq<string> output, Func<Unit, IO<A>> next) | |
{ | |
Path = path; | |
Output = output; | |
Next = 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 = output; | |
Next = next; | |
} | |
} | |
} | |
} |
Also it would be interesting to see how bigger is the code without LanguageExt. I assume not so much. Then it will be a proof that no magic required for doing FP in C#.
I did one without LanguageExt as proof: https://gist.github.com/dadhi/026350db603dd348af6c0369a9a5bd0b
Hey, I wanted to understand in detail how the Free monad works and what it solves and so, ended up with a newer example with monadic boilerplate stripped off: https://gist.github.com/dadhi/59cfc698f6dc6e31b722cd804aae185a
seems can use discards instead of _1
normal C# with MoreLinq could do well zip(Range(1, Int32.MaxValue), lines) .Map((i, line) => $"{i} {line}") .ToSeq()
Here is the same example implemented using an alternative approach to free monads in C#: https://github.com/yuretz/FreeAwait/blob/master/samples/TestIO/Main.cs
When creating ADT cases the constructors may be even shortened with ValueTuples ;)
Btw, thanks for the cool complete example!