When and how to use continuation passing style

To answer your last question, continuation passing style is not the same a currying. You curry when you create a function out of another function, by specifying one, or more of its parameters, thus getting a function with fewer parameters. Currying in a functional programming language, such as F#, and C# to an extent, allows you to treat all functions as being a function of one variable. If the said function has more than one parameter, then it can be viewed as having on parameter and returning another function with the remaining parameters. This is an example of currying in c#:

public static class FuncExtensions
{
        public static Func<A, Func<R>> Curry<A, R>(this Func<A, R> f)
        {
            return a => () => f(a);
        }
}

Func<int, int> f = x => x + 1;

Func<int> curried = f.Curry()(1);

Where the function curried will always return 2. There are of course, more enlightening uses of this.

In regards to continuation passing style, in addition to the Wes Dyer blog linked to, look into F# async workflows, which are instances of continuations, or the continuation monad. You can try to use the term continuation monad to find some additional articles.


I have found a short and good explanation for CPS in the book “Concurrency in .NET, Modern patterns of concurrent and parallel programming” by Riccardo Terrell (the code is in C#):

Sometimes, optimized tail-call recursive functions aren’t the right solution or can be difficult to implement. In this case, one possible alternative approach is CPS, a technique to pass the result of a function into a continuation. CPS is used to optimize recursive functions because it avoids stack allocation. Moreover, CPS is used in the Microsoft TPL, in async/await in C#, and in async-workflow in F#. CPS plays an important role in concurrent programming. This following code example shows how the CPS pattern is used in a function GetMaxCPS:

static void GetMaxCPS(int x, int y, Action<int> action) => action(x > y ? x : y);

GetMaxCPS (5, 7, n => Console.WriteLine(n));

The argument for the continuation passing is defined as a delegate Action, which can be used conveniently to pass a lambda expression. The interesting part is that the function with this design never returns a result directly; instead, it supplies the result to the continuation procedure. CPS can also be used to implement recursive functions using tail calls.

Here is also a simple and short explanation from Wikipedia. The explanation also mentions the benefits of CPS:

https://en.wikipedia.org/wiki/Continuation-passing_style

A function written in continuation-passing style takes an extra argument: an explicit "continuation", i.e. a function of one argument. When the CPS function has computed its result value, it "returns" it by calling the continuation function with this value as the argument. That means that when invoking a CPS function, the calling function is required to supply a procedure to be invoked with the subroutine's "return" value. Expressing code in this form makes a number of things explicit which are implicit in direct style. These include: procedure returns, which become apparent as calls to a continuation; intermediate values, which are all given names; order of argument evaluation, which is made explicit; and tail calls, which simply call a procedure with the same continuation, unmodified, that was passed to the caller.

Programming with continuations can also be useful when a caller does not want to wait until the callee completes. For example, in user-interface (UI) programming, a routine can set up dialog box fields and pass these, along with a continuation function, to the UI framework. This call returns right away, allowing the application code to continue while the user interacts with the dialog box. Once the user presses the "OK" button, the framework calls the continuation function with the updated fields.


An excellent write-up on CPS in C# context was made by Wes Dyer. I don't think I could possibly add anything meaningful to that. If some specific things are unclear there, go ahead and ask about them.

Tags:

C#