-
-
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!