Last active
December 15, 2015 07:38
-
-
Save ldematte/5224484 to your computer and use it in GitHub Desktop.
Testing different solutions for async (continuation) Monads in C#, including using LINQ Syntax
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
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
namespace Dematte.Tests | |
{ | |
// Reproduce the Async/Workflow pattern from F# | |
// using C# classes, complete with a continuation monad. | |
// For learning purposes only | |
public class MyAsyncParams<T> | |
{ | |
public MyAsyncParams(Action<T> cont) | |
{ | |
this.cont = cont; | |
} | |
public Action<T> cont; | |
} | |
public class MyAsync<T> | |
{ | |
Func<MyAsyncParams<T>, Unit> myAsyncFunc; | |
public MyAsync(Func<MyAsyncParams<T>, Unit> func) | |
{ | |
this.myAsyncFunc = func; | |
} | |
public void Apply(MyAsyncParams<T> args) | |
{ | |
myAsyncFunc(args); | |
} | |
} | |
public class AsyncUtils | |
{ | |
public static MyAsync<U> Bind<T, U>(MyAsync<T> e, Func<T, MyAsync<U>> rest) | |
{ | |
Func<MyAsyncParams<U>, Unit> myAsyncFunc = delegate(MyAsyncParams<U> args) | |
{ | |
Action<T> cont = delegate(T a) | |
{ | |
var res = rest(a); | |
res.Apply(args); | |
}; | |
var otherArgs = new MyAsyncParams<T>(cont); | |
e.Apply(otherArgs); | |
// Can be omitted transforming Func<MyAsyncParams<U>, Unit> into | |
// -> Action<MyAsyncParams<U>> | |
return Unit.One; | |
}; | |
return new MyAsync<U>(myAsyncFunc); | |
} | |
} | |
// Only here to see the steps of contruction | |
public abstract class MyIntermediateBaseAsync<T> | |
{ | |
//Changed the myAsyncFunc from a delegate to an overridable function | |
public abstract void myAsyncFunc(Action<T> args); | |
public void Apply(Action<T> args) | |
{ | |
myAsyncFunc(args); | |
} | |
} | |
// My base asynch in C# | |
public abstract class MyBaseAsync<T> | |
{ | |
//Fused myAsyncFunc and Apply | |
public abstract void Apply(Action<T> args); | |
} | |
public class MyBindAsync<T, U> : MyBaseAsync<U> | |
{ | |
private MyBaseAsync<T> e; | |
private Func<T, MyBaseAsync<U>> rest; | |
public MyBindAsync(MyBaseAsync<T> e, Func<T, MyBaseAsync<U>> rest) | |
{ | |
this.e = e; | |
this.rest = rest; | |
} | |
public override void Apply(Action<U> args) | |
{ | |
Action<T> cont = delegate(T a) | |
{ | |
var res = rest(a); | |
res.Apply(args); | |
}; | |
e.Apply(cont); | |
} | |
} | |
public class MyIarAsync<T> : MyBaseAsync<T> | |
{ | |
private Func<System.AsyncCallback, object, System.IAsyncResult> beginFunc; | |
private Func<System.IAsyncResult, T> endFunc; | |
public MyIarAsync(Func<System.AsyncCallback, object, System.IAsyncResult> beginFunc, | |
Func<System.IAsyncResult, T> endFunc) | |
{ | |
this.beginFunc = beginFunc; | |
this.endFunc = endFunc; | |
} | |
public override void Apply(Action<T> args) | |
{ | |
var callback = new System.AsyncCallback(delegate(IAsyncResult iar) | |
{ | |
var retval = endFunc(iar); | |
args(retval); | |
}); | |
beginFunc(callback, null); | |
} | |
} | |
// let's change names | |
// What is that takes a function and, when some conditions are met, | |
// runs it? | |
public interface IContinuable<T> | |
{ | |
void ContinueWith(Action<T> args); | |
} | |
// Now try to reverse it again: from classes to function. | |
// This should allow us 1) (Maybe!) more efficiency: every Bind is a heap allocation | |
// (but then calling a virtual method is pretty quick) | |
// 2) modify method names, change parameter types slightly -> have linq syntax | |
public static class Continuable | |
{ | |
// The OO versione of building a continuation that is the "binding" (composition?) of two | |
// continuations | |
public static IContinuable<U> Bind<T, U>(IContinuable<T> e, Func<T, IContinuable<U>> rest) | |
{ | |
return new BindContinuables<T, U>(e, rest); | |
} | |
// And its functional version, obtained by reversing our original process. | |
// IContinuable<T> -> Action<Action<T>> (or Func<Func<T, Unit>, Unit>, or Func<Func<T, Answer>, Answer>?) | |
// or delegate Answer Continuation<T,Answer>(Func<T,Answer> k); | |
public static Action<Action<U>> Bind<T, U>(Action<Action<T>> e, Func<T, Action<Action<U>>> f) | |
{ | |
Action<Action<U>> ret = delegate(Action<U> args) | |
{ | |
Action<T> cont = delegate(T a) | |
{ | |
var res = f(a); | |
res(args); | |
}; | |
e(cont); | |
}; | |
return ret; | |
} | |
// Wow! Same result as http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx | |
//public static K<U, Answer> Bind<T, U, Answer>(this K<T, Answer> m, Func<T, K<U, Answer>> k) | |
//return (Func<U,Answer> c) => m((T x) => k(x)(c)); | |
// A SelectMany verision of the bind, to use with linq query syntax | |
public static Action<Action<V>> SelectMany<T, U, V>(this Action<Action<T>> m, Func<T, Action<Action<U>>> k, Func<T, U, V> s) | |
{ | |
//return m.SelectMany(x => k(x).SelectMany(y => s(x, y).ToContinuation<V, Answer>())); | |
return Bind(m, x => Bind(k(x), (y => Unit<V>(s(x, y))))); | |
} | |
// The OO version of building a continuation from APM | |
public static IContinuable<T> FromApm<T>(Func<System.AsyncCallback, object, System.IAsyncResult> beginFunc, | |
Func<System.IAsyncResult, T> endFunc) | |
{ | |
return new ApmContinuable<T>(beginFunc, endFunc); | |
} | |
// And its functional counterpart | |
public static Action<Action<T>> ContFromApm<T>(Func<System.AsyncCallback, object, System.IAsyncResult> beginFunc, | |
Func<System.IAsyncResult, T> endFunc) | |
{ | |
Action<Action<T>> ret = delegate(Action<T> args) | |
{ | |
var callback = new System.AsyncCallback(delegate(IAsyncResult iar) | |
{ | |
var retval = endFunc(iar); | |
args(retval); | |
}); | |
beginFunc(callback, null); | |
}; | |
return ret; | |
} | |
//public static K<T, Answer> ToContinuation<T, Answer>(this T value) | |
//return (Func<T, Answer> c) => c(value); | |
public static Action<Action<T>> Unit<T>(T value) | |
{ | |
return (Action<T> c) => c(value); | |
} | |
// OO version is simple, because we made event implement directly IContinuable. | |
// It is not even necessary to call FromEvent, it just works | |
public static IContinuable<T> FromEvent<T>(P.Evt<T> evt) | |
{ | |
return evt; | |
} | |
// The functional version | |
public static Action<Action<T>> FakeSync<T>(this P.Evt<T> evt) | |
{ | |
return (Action<T> c) => evt.Async(c); | |
} | |
// Let, F# style | |
public static Action<Action<U>> Let<T, U>(T e, Func<T, Action<Action<U>>> rest) | |
{ | |
return (Action<U> args) => | |
{ | |
var res = rest(e); | |
res(args); | |
}; | |
} | |
//Let -> select? Which relationship? | |
public static Action<Action<U>> Select<U, T>(this Action<Action<T>> m, Func<T, U> k) | |
{ | |
return c => m(x => c(k(x))); | |
} | |
} | |
public class BindContinuables<T, U> : IContinuable<U> | |
{ | |
private IContinuable<T> e; | |
private Func<T, IContinuable<U>> rest; | |
public BindContinuables(IContinuable<T> e, Func<T, IContinuable<U>> rest) | |
{ | |
this.e = e; | |
this.rest = rest; | |
} | |
// just a test.. does this Pure attribute really do something?? | |
//[Pure] .NET 4 | |
public void ContinueWith(Action<U> args) | |
{ | |
Action<T> cont = delegate(T a) | |
{ | |
var res = rest(a); | |
res.ContinueWith(args); | |
}; | |
e.ContinueWith(cont); | |
} | |
} | |
public class LetContinuables<T, U> : IContinuable<U> | |
{ | |
private T e; | |
private Func<T, IContinuable<U>> rest; | |
public LetContinuables(T e, Func<T, IContinuable<U>> rest) | |
{ | |
this.e = e; | |
this.rest = rest; | |
} | |
public void ContinueWith(Action<U> args) | |
{ | |
var res = rest(e); | |
res.ContinueWith(args); | |
} | |
} | |
//member b.Combine(p1,p2) = bindA p1 (fun () -> p2) | |
//public void ContinueWith(Action<T> args) | |
// { | |
// Func<IContinuable<T>> rest = delegate() { return two; }; | |
// Action<T> cont = delegate(T a) | |
// { | |
// var res = rest(); | |
// res.ContinueWith(args); | |
// }; | |
// one.ContinueWith(cont); | |
// } | |
public class SequenceContinuables<T> : IContinuable<T> | |
{ | |
private IContinuable<T> one; | |
private IContinuable<T> two; | |
public SequenceContinuables(IContinuable<T> one, IContinuable<T> two) | |
{ | |
this.one = one; | |
this.two = two; | |
} | |
public void ContinueWith(Action<T> args) | |
{ | |
Action<T> cont = delegate(T a) | |
{ | |
two.ContinueWith(args); | |
}; | |
one.ContinueWith(cont); | |
} | |
} | |
public class ApmContinuable<T> : IContinuable<T> | |
{ | |
private Func<System.AsyncCallback, object, System.IAsyncResult> beginFunc; | |
private Func<System.IAsyncResult, T> endFunc; | |
public ApmContinuable(Func<System.AsyncCallback, object, System.IAsyncResult> beginFunc, | |
Func<System.IAsyncResult, T> endFunc) | |
{ | |
this.beginFunc = beginFunc; | |
this.endFunc = endFunc; | |
} | |
public void ContinueWith(Action<T> args) | |
{ | |
var callback = new System.AsyncCallback(delegate (IAsyncResult iar) { | |
var retval = endFunc(iar); | |
args(retval); | |
}); | |
beginFunc(callback, null); | |
} | |
} | |
// Someone that has to wait for anyone, but can just "schedule to continue" | |
// This is the "return" of Haskell and F#, or the "unit" operation for monads | |
public class UnitContinuable<T> : IContinuable<T> | |
{ | |
private T a; | |
public UnitContinuable() { } | |
public UnitContinuable(T a) { this.a = a; } | |
public void ContinueWith(Action<T> args) | |
{ | |
args(a); | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment