monadic comprehensions and functional composition with query expressions
DESCRIPTION
Build monads using the C# language with a C# style, then use the appropriate methods to ensure the LINQ query syntax works with this functional design pattern. After describing monads, we will cut the middleman and apply the same techniques directly to objects and functions to achieve better results with a declarative syntax.TRANSCRIPT
Monadic Comprehensions and Functional Composition with
Query Expressions
Chris Eargle
LINQ to Objects
Oh, that’s a monad!
List Monad
LINQ to Objectsfrom x in new[] {1, 2, 3}from y in new[] {1, 2, 3}select Tuple.Create(x, y)
List Monaddo x <- [1,2,3] y <- [1,2,3] return (x,y)
Query Expressions
var words = new[] { "the", "quick", "brown", "fox"};
IEnumerable<string> query = from word in words select word;
var query = from word in words orderby word.Length select word;
var query = from word in words orderby word.Length select word.Replace(‘o’, ‘a’);
var query = words.OrderBy(w => w.Length) .Select(w => w.Replace('o', 'a'));
Monad
Functional Design Pattern
ReturnBind
public struct Monad<T> { private T value;
public Monad(T value) { this.value = value; }
public Monad<U> Bind<U>(Func<T, Monad<U>> func) { return func(value); } }
public static class Monad { public static Monad<T> Create<T>(T value) { return new Monad<T>(value); } }
var monad = Monad.Create(1);var monad2 = monad.Bind(v => Monad.Create(v));
Assert.IsInstanceOfType(monad, typeof(Monad<int>));Assert.AreEqual(monad, monad2);Assert.AreNotSame(monad, monad2);
Identity Monad
public struct Identity<T> { T value; public T Value { get { return value; } } public Identity(T value) { this.value = value; } }
public static class Identity { public static Identity<T> Create<T>(T value) { return new Identity<T>(value); } }
var result = from x in Identity.Create(1) select x.ToString();
Assert.AreEqual("1", result.Value);
public Identity<TResult> Select<TResult>(Func<T, TResult> selector)
{ return Identity.Create(selector(value)); }
var result = from x in Identity.Create(2) from y in Identity.Create(3) select x + y;
Assert.AreEqual(5, result.Value);
public Identity<TResult> SelectMany<U, TResult>( Func<T, Identity<U>> selector, Func<T, U, TResult> resultSelector){ return Identity.Create(resultSelector(value,
selector(value).Value));}
var result = from x in Identity.Create(2) from y in Identity.Create(3) select x + y;
Assert.AreEqual(5, result.Value);
Cut the Middle Man
var result = from x in 2 from y in 3 select x + y;
Assert.AreEqual(5, result);
public static TResult SelectMany<T1, T2, TResult>( this T1 source, Func<T1, T2> selector, Func<T1, T2, TResult> resultSelector){ return resultSelector(source, selector(source));}
Tuples
var result = from info in ConferenceInfo() from dates in ConferenceDates() select new { Name = info.Item1, StartDate = dates.Item1, EndDate = dates.Item3 };
Assert.AreEqual("IEEE ICCSIT", result.Name);
Assert.AreEqual(july9th, result.StartDate);
Assert.AreEqual(july11th, result.EndDate);
Continuation Monad
public class Continuation<R, A> { private readonly Func<Func<A, R>, R> value; internal Continuation(Func<Func<A, R>, R> func) { this.value = func; } public R Run(Func<A, R> k) { return value(k); }}
public static class Continuation { public static Continuation<R, A> Create<R, A>(Func<A> func) { return Continuation.Create<R, A>(k => k(func())); } public static Continuation<R, A> Create<R, A>(Func<Func<A, R>, R> func) { return new Continuation<R, A>(func); } }
public static Continuation<R, int> Square<R>(int x){ return Continuation.Create<R, int>(() => x * x);}
public static Continuation<R, double> Hypotenuse<R>(int x, int y)
{ return from h in Square<R>(x) from w in Square<R>(y) select Math.Sqrt(h + w);}
Assert.AreEqual(5, Hypotenuse<double>(3, 4).Run(n => n));
Cut the Middle Man
Func<int, int> square = x => x * x;Func<int, double> squareRoot = x => Math.Sqrt(x);
var hypotenuse = from h in square from w in square select squareRoot(h + w);
Assert.AreEqual(5, hypotenuse(3, 4));
public static Func<T1, T2, TResult3>SelectMany<T1, T2, TResult, TResult2, TResult3>( this Func<T1, TResult> leftFunc, Func<T1, Func<T2, TResult2>> rightFunc, Func<TResult, TResult2, TResult3> selector) { return (x, y) => selector(leftFunc(x), rightFunc(x)(y)); }
Func<int, int> f = x => x + 2;
var result = from x in f select x * 2;
Assert.AreEqual(6, result(1));
public static Func<T, TResult2> Select <T, TResult, TResult2>( this Func<T, TResult> func, Func<TResult, TResult2> selector) { return a => selector(func(a)); }
public static Func<Int32, Double> Sum(this Func<Int32, Double> func, int start = 0) { return end => { Double result = default(Double); for (int k = start; k <= end; k++) { result += func(k); } return result; }; }
Factorial
Func<int, int> f = x => x;
var factorial = from x in f.Sum() where x != 0 select x ?? 1;
Assert.AreEqual(1, factorial(0)); Assert.AreEqual(6, factorial(3));
public static Func<T, dynamic> Where<T, TResult>( this Func<T, TResult> func, Func<TResult, bool> predicate){ return a => { TResult result = func(a); return predicate(result) ? result as dynamic : null; };}
Func<Int32, Double> f = k => 4 * Math.Pow(-1, k) / (2.0 * k + 1); var calculatePi = f.Sum(); Assert.AreEqual(3.14159, calculatePi(200000), 7);
monadic.codeplex.com