One can approach the notion of continuation from many different angles. I would like to start with asynchronous function calls in Javascript (JS), which are often employed for the purpose of eliminating potential blocking due to inputs or resources needed for execution being yet to be made available.
Let us use do_async1 and do_async2 to refer to two functions such that calls made to them are supposed to be executed asynchronously (so as to avoid potential blocking of entire program execution):
In the body of the following function do_async12, a call to do_async1 is followed by another call to do_async2:// fun do_async12(): void = { val () = do_async1(); val () = do_async2() } (* end of [do_async12] *) //
// fun do_async12(): void = { val () = do_async1_cont(lam() => do_async2()) } (* end of [do_async12] *) //
// fun do_async12_cont (k: cont0()): void = { val () = do_async1_cont(lam() => do_async2_cont(k)) } (* end of [do_async12] *) //
In the following presentation, I would like to present a few more examples that contrast the direct style with the CPS-style. Let cont1 be declared as follows:
The functions fact and k_fact given below are implemented in the direct style and the CPS-style, respectively:// fun fact ( n : int ) : int = if n > 0 then n * fact(n-1) else 1 // (* ****** ****** *) // fun k_fact ( n : int , k : cont1(int) ) : void = ( // if (n > 0) then k_fact(n-1, lam(res) => k(n*res)) else k(1) // end of [if] ) (* end of [k_fact] *) //
The next example is slightly more involved. The functions fibo and k_fibo given below are implemented in the direct style and the CPS-style, respectively:
// fun fibo ( n : int ) : int = if n >= 2 then fibo(n-1)+fibo(n-2) else n // (* ****** ****** *) // fun k_fibo ( n : int , k : cont1(int) ) : void = ( // if (n >= 2) then k_fibo ( n-1 , lam(r1) => k_fibo(n-2, lam(r2) => k(r1+r2)) ) (* end of [then] *) else k(n) // end of [else] // end of [if] ) (* end of [k_fibo] *) //
Going from fact to k_fact and fibo to k_fibo is often referred to as CPS-translation. Note that the recursive calls in the bodies of k_fact and k_fibo are all tail-calls. In general, each recursive call in an implementation written in the direct style should be translated into a tail-recursive one in the corresponding implementation written in the CPS-style. If a recursive call in the direct style happens to be tail-recursive, then the corresponding tail-recursive call in the CPS-style is passed the same continuation as the one passed to its caller. For instance, the following example shows a tail-recursive function (fact2) in the direct style being translated into one (k_fact2) in the CPS-style:
// fun fact2 (n : int, res: int): int = if n > 0 then fact2(n-1, n*res) else res // (* ****** ****** *) // fun k_fact2 (n: int, res: int, k: cont1(int)): void = if n > 0 then k_fact2(n-1, n*res, k) else k(res) //
As yet another example, let us see list0_map of the direct style contrasted with list0_kmap of the CPS-style:
// extern fun {a:t@ype} {b:t@ype} list0_map (xs: list0(INV(a)), f0: cfun(a, b)): list0(b) extern fun {a:t@ype} {b:t@ype} list0_kmap ( xs: list0(INV(a)) , f0: cfun(a, cont1(b), void), k0: cont1(list0(b))): void // (* ****** ****** *) // implement {a}{b} list0_map(xs, f0) = ( case+ xs of | list0_nil() => list0_nil() | list0_cons(x, xs) => list0_cons(f0(x), list0_map<a><b>(xs, f0)) ) // implement {a}{b} list0_kmap(xs, f0, k0) = ( case+ xs of | list0_nil() => k0(list0_nil()) | list0_cons(x, xs) => f0(x, lam(y) => list0_kmap<a><b>(xs, f0, lam(ys) => k0(list0_cons(y, ys)))) ) //
The last example in this chapter is the following stream-processing function written in CPS-style:
// extern fun {a:t@ype} stream_kforeach ( xs: stream(INV(a)) , f0: cfun(a, cont1(bool), void), k0: cont0()): void implement {a}(*tmp*) stream_kforeach(xs, f0, k0) = ( case+ !xs of | stream_nil() => k0() | stream_cons(x, xs) => f0(x, lam(y) => if y then stream_kforeach<a>(xs, f0, k0) else k0()) ) //
Please find on-line the entirety of the code used in this chapter. The mentioned URL link(s) can be found as follows: