826 words
4 minutes
Category Theory via C# (22) More Monad: Continuation Monad

[LINQ via C# series]#

[Category Theory via C# series]#

Latest version: https://weblogs.asp.net/dixin/category-theory-via-csharp-8-more-linq-to-monads#

Continuation and continuation-passing style#

In C#, callback is frequently used. For example, a very simple Add function, without asynchrony:

// [Pure]
public static partial class Cps
{
// Add = (x, y) => x + y
public static int Add
(int x, int y) => x + y;
}

With a callback, it becomes:

// AddWithCallback = (x, y, callback) => callback(x + y)
public static TCallback AddWithCallback<TCallback>
(int x, int y, Func<int, TCallback> callback) => callback(x + y);

In functional programming, a continuation is like a callback. Continuation-passing style (CPS) is a style of programming to pass the control to a continuation, just like in above example, (x + y) is calculated then passed to the callback.

For convenience, AddWithCallback can be curried a little bit:

// AddWithCallback = (x, y) => callback => callback(x + y)
public static Func<Func<int, TCallback>, TCallback> AddWithCallback<TCallback>
(int x, int y) => callback => callback(x + y);

So that all the apperances of TCallback are in the return type Func<Func<int, TCallback>, TCallback>, then an alias Cps can be defined to make the code shorter:

// Cps<T, TContinuation> is alias of Func<Func<T, TContinuation>, TContinuation>
public delegate TContinuation Cps<out T, TContinuation>(Func<T, TContinuation> continuation);

Now a continuation-passing style function AddCps can be defined, which is exactly the same as above:

// AddCps = (x, y) => continuation => continuation(x + y)
public static Cps<int, TContinuation> AddCps<TContinuation>
(int x, int y) => continuation => continuation(x + y);

Other examples are the SquareCps and SumOfSquareCps functions:

// SquareCps = x => continuation => continuation(x * x)
public static Cps<int, TContinuation> SquareCps<TContinuation>
(int x) => continuation => continuation(x * x);
// SumOfSquaresCps = (x, y) => continuation => SquareCps(x)(xx => SquareCps(y)(yy => AddCps(xx)(yy)(continuation)));
public static Cps<int, TContinuation> SumOfSquaresCps<TContinuation>
(int x, int y) => continuation =>
SquareCps<TContinuation>(x)(xx =>
SquareCps<TContinuation>(y)(yy =>
AddCps<TContinuation>(xx, yy)(continuation)));

In this case, CPS makes code and algorithms harder to understand and maintain.

Continuation monad#

SelectMany can be implemented for Cps<T, TContinuation>:

[Pure]
public static partial class CpsExtensions
{
// Required by LINQ.
public static Cps<TResult, TContinuation> SelectMany<TSource, TSelector, TResult, TContinuation>
(this Cps<TSource, TContinuation> source,
Func<TSource, Cps<TSelector, TContinuation>> selector,
Func<TSource, TSelector, TResult> resultSelector) =>
continuation => source(sourceArg =>
selector(sourceArg)(selectorArg =>
continuation(resultSelector(sourceArg, selectorArg))));
// Not required, just for convenience.
public static Cps<TResult, TContinuation> SelectMany<TSource, TResult, TContinuation>
(this Cps<TSource, TContinuation> source, Func<TSource, Cps<TResult, TContinuation>> selector) =>
source.SelectMany(selector, Functions.False);
}

so that:

// [Pure]
public static partial class CpsExtensions
{
// η: T -> Cps<T, TContinuation>
public static Cps<T, TContinuation> Cps<T, TContinuation>
(this T arg) => continuation => continuation(arg);
// φ: Lazy<Cps<T1, TContinuation>, Cps<T2, TContinuation>> => Cps<Lazy<T1, T2>, TContinuation>
public static Cps<Lazy<T1, T2>, TContinuation> Binary<T1, T2, TContinuation>
(this Lazy<Cps<T1, TContinuation>, Cps<T2, TContinuation>> binaryFunctor) =>
binaryFunctor.Value1.SelectMany(
value1 => binaryFunctor.Value2,
(value1, value2) => new Lazy<T1, T2>(value1, value2));
// ι: TUnit -> Cps<TUnit, TContinuation>
public static Cps<Unit, TContinuation> Unit<TContinuation>
(Unit unit) => unit.Cps<Unit, TContinuation>();
// Select: (TSource -> TResult) -> (Cps<TSource, TContinuation> -> Cps<TResult, TContinuation>)
public static Cps<TResult, TContinuation> Select<TSource, TResult, TContinuation>
(this Cps<TSource, TContinuation> source, Func<TSource, TResult> selector) =>
// continuation => source(sourceArg => continuation(selector(sourceArg)));
// continuation => source(continuation.o(selector));
source.SelectMany(value => selector(value).Cps<TResult, TContinuation>());
}

Cps< , > is a monad, monoidal functor, and functor.

Monad laws, and unit tests#

2 helper functions can be created:

// [Pure]
public static partial class CpsExtensions
{
public static Func<T, TContinuation> NoCps<T, TContinuation>
(this Func<T, Cps<TContinuation, TContinuation>> cps) => arg => cps(arg)(Functions.Id);
public static T Invoke<T>
(this Cps<T, T> cps) => cps(Functions.Id);
}

So the unit test becomes easy:

public partial class MonadTests
{
[TestMethod()]
public void ContinuationTest()
{
bool isExecuted1 = false;
Func<int, Func<int, Func<string, string>>> f = x => y => z =>
{
isExecuted1 = true;
return (x + y + z.Length).ToString(CultureInfo.InstalledUICulture);
};
Cps<string, int> query = from x in 1.Cps<int, int>()
from y in 2.Cps<int, int>()
from z in "abc".Cps<string, int>()
select f(x)(y)(z);
Assert.IsFalse(isExecuted1); // Laziness.
Assert.AreEqual((1 + 2 + "abc".Length).ToString(CultureInfo.InstalledUICulture).Length, query(x => x.Length)); // Execution.
Assert.IsTrue(isExecuted1);
// Monad law 1: m.Monad().SelectMany(f) == f(m)
Func<int, Cps<int, int>> addOne = x => (x + 1).Cps<int, int>();
Cps<int, int> left = 1.Cps<int, int>().SelectMany(addOne);
Cps<int, int> right = addOne(1);
Assert.AreEqual(left.Invoke(), right.Invoke());
// Monad law 2: M.SelectMany(Monad) == M
Cps<int, int> M = 1.Cps<int, int>();
left = M.SelectMany(CpsExtensions.Cps<int, int>);
right = M;
Assert.AreEqual(left.Invoke(), right.Invoke());
// Monad law 3: M.SelectMany(f1).SelectMany(f2) == M.SelectMany(x => f1(x).SelectMany(f2))
Func<int, Cps<int, int>> addTwo = x => (x + 2).Cps<int, int>();
left = M.SelectMany(addOne).SelectMany(addTwo);
right = M.SelectMany(x => addOne(x).SelectMany(addTwo));
Assert.AreEqual(left.Invoke(), right.Invoke());
}
}

And following is unit test for continuation functor, which also demonstrate the CPS version of factorial:

public partial class FunctorTests
{
[TestMethod()]
public void ContinuationTest()
{
Func<int, Cps<int, int>> factorialCps = null; // Must have.
factorialCps = x => x == 0
? 1.Cps<int, int>()
: (from y in factorialCps(x - 1)
select x * y);
Func<int, int> factorial = factorialCps.NoCps();
Assert.AreEqual(3 * 2 * 1, factorial(3));
// Functor law 1: F.Select(Id) == Id(F)
Assert.AreEqual(factorialCps(3).Select(Functions.Id).Invoke(), Functions.Id(factorialCps(3)).Invoke());
// Functor law 2: F.Select(f2.o(f1)) == F.Select(f1).Select(f2)
Func<int, int> addOne = x => x + 1;
Func<int, int> addTwo = x => x + 2;
Cps<int, int> cps1 = factorialCps(3).Select(addTwo.o(addOne));
Cps<int, int> cps2 = factorialCps(3).Select(addOne).Select(addTwo);
Assert.AreEqual(cps1.Invoke(), cps2.Invoke());
}
}
Category Theory via C# (22) More Monad: Continuation Monad
https://dixin.github.io/posts/category-theory-via-c-sharp-22-more-monad-continuation-monad/
Author
Dixin
Published at
2018-12-23
License
CC BY-NC-SA 4.0