I am coming from a OOP, non-functional background, so I am having trouble fully visualizing several online examples regarding continuation passing. Also, functional languages like Scheme don't have to specify types of arguments or return values, so I am unsure whether I got the idea correctly.
Since C# supports lambdas, I took the first example from the Wikipedia article and tried to port it to C# with strong typing, to see how the pattern would apply:
// (Scheme)
// direct function
(define (pyth x y)
(sqrt (+ (* x x) (* y y))))
// rewriten with CPS
(define (pyth& x y k)
(*& x x (lambda (x2)
(*& y y (lambda (y2)
(+& x2 y2 (lambda (x2py2)
(sqrt& x2py2 k))))))))
// where *&, +& and sqrt& are defined to
// calculate *, + and sqrt respectively and pass the result to k
(define (*& x y k)
(k (* x y)))
So, rewriting the CPS pyth&
version in C# resulted in:
// (C#6)
// continuation function signature
delegate double Cont(double a);
// *&, +& and sqrt& functions
static double MulCont(double a, double b, Cont k) => k(a * b);
static double AddCont(double a, double b, Cont k) => k(a + b);
static double SqrtCont(double a, Cont k) => k(Math.Sqrt(a));
// sqrt(x*x + y*y), cps style
static double PythCont(double x, double y, Cont k) =>
MulCont(x, x, x2 =>
MulCont(y, y, y2 =>
AddCont(x2, y2, x2py2 =>
SqrtCont(x2py2, k))));
I could have used generics instead of double
, but signatures would be longer. Anyway, what I am not sure is:
Is the
Cont
signature above correct (i.e.Func<double, double>
)? Should the continuation fn. accept the parameter, process it, and then return the value of the same type back?When I first started reading about continuations, I got the feeling that this continuation function will get invoked for each step in the call stack, but in the example above it's only passed to
sqrt&
, and all other calls get lambdas which don't really "pass" intermediate values to the original continuation. The code above in the function above is basically analogue tok(Math.Sqrt(x * x + y * y))
, so does this mean my assumption about intermediate "hooks" is wrong?
call/cc
is more difficult to support than tail calls. – Mosherasync
/await
as continuation examples for C# (in C#, continuations are used pretty much exclusively in the context of async tasks). – Temporizecall/cc
is for. It's a way to get the rest of the computation as a function you can indeed choose not to call, call once or several times. CPS is what languages that supportcall/cc
to do with the code in order to facilitate that one can write code in a more normal fashion but get the benefits of continuations without having to write code in CPS style themselves. It's quite difficult to follow even the simple example of pythagoras so imagine a complex program in the same form. – Moshertail.
prefix to allow tail recursion (which the CLR just does fine). You could use something like post process the IL though. – Jemappestail.
prefix bytecode in IL for the call in the tail position. – Jemappestail.
prefix without having to postprocess like you say you have to for C#. – Armeldacar
/cdr
and (maybe) even things likelength
(which is tail recursive in itself, but cannot recurse more). Edit:car
/cdr
is a bad example as I only emit field getters for them anyways, but arithmetic is probably better example. – Jemappes