Categorical Structure of Continuation Passing Style

Hayo Thielecke

Doctor of Philosophy University of Edinburgh

Abstract

This thesis attempts to make precise the structure inherent in Continuation Passing Style (CPS).

We emphasize that CPS translates λ-calculus into a very basic calculus that does not have functions as primitive.

We give an abstract categorical presentation of continuation semantics by taking the continuation type constructor ¬ (or cont in Standard ML of New Jersey) as primitive. This constructor on types extends to a contravariant functor on terms which is adjoint to itself on the left; restricted to the subcategory of those programs that do not manipulate the current continuation, it is adjoint to itself on the right.

The motivating example of such a category is built from (equivalence classes of typing judgements for) continuation passing style (CPS) terms. The categorical approach suggests a notion of effect-free term as well as some operators for manipulating continuations. We use these for writing programs that illustrate our categorical approach and refute some conjectures about control effects.

A call-by-value λ-calculus with the control operator callcc can be interpreted. Arrow types are broken down into continuation types for argument/resultcontinuations pairs, reflecting the fact that CPS compiles functions into a special case of continuations. Variant translation are possible, among them “lazy” callby-name, which can be derived by way of argument thunking, and a genuinely call-by-name transform. Specialising the semantics to the CPS term model allows a rational reconstruction of various CPS transforms.

Acknowledgements

I would like to thank my supervisors, Stuart Anderson and John Power.

Thanks for discussions and comments to Olivier Danvy, Matthias Felleisen, Andrzej Filinski, Masahito “Hassei” Hasegawa, John Hatcliff, Peter O’Hearn, Alan Paxton, Andy Pitts, Jon Riecke, David N. Turner and Phil Wadler.

Thanks to Glynn Winskel for inviting me to Aarhus for a week.

Diagrams were typeset with XY-pic.

I am grateful to my parents for moral and financial support throughout. To them this thesis is dedicated.

Declaration

I declare that this thesis was composed by myself and that the work contained therein is my own, except where explicitly stated otherwise in the text.

(Hayo Thielecke)

Table of Contents

List of Programs 4

Chapter 1 Introduction 6

1.1 An introduction to continuations in programming languages … 7 1.1.1 An example program . . . . . . . . . . . . . . . . . . . . . 9 1.1.2 Upward continuations . . . . . . . . . . . . . . . . . . . . 14 1.1.3 Continuation Passing Style . . . . . . . . . . . . . . . . . . 15 1.1.4 CPS as name-passing . . . . . . . . . . . . . . . . . . . . 17

1.2 Introducing the continuation functor . . . . . . . . . . . . . . . . 18 1.2.1 The self-adjointness of higher-order jumping . . . . . . . . 21 1.2.2 Alternative control operators . . . . . . . . . . . . . . . . . 24

1.3 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 1.4 Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

Chapter 2 The CPS calculus 31 2.1 CPS calculus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.2 Recursive CPS calculus . . . . . . . . . . . . . . . . . . . . . . . 34 2.3 Operational semantics for CPS . . . . . . . . . . . . . . . . . . . 35

2.3.1 Observational congruence . . . . . . . . . . . . . . . . . . 35 2.4 Linear CPS calculus . . . . . . . . . . . . . . . . . . . . . . . . . 36

2.4.1 Linear unary CPS calculus . . . . . . . . . . . . . . . . . 37 2.5 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.6 Translation from CPS calculus . . . . . . . . . . . . . . . . . . . . 38 2.7 Idioms and jargon for the CPS calculus . . . . . . . . . . . . . . 42

Chapter 3 CPS transforms 44 3.1 A survey of CPS transforms . . . . . . . . . . . . . . . . . . . . . 45 3.2 A simplified notation for non-recursive CPS . . . . . . . . . . . . 49 3.3 Soundness of the uncurrying call-by-name CPS transform …. 51 3.4 CPS transforms to the λ- and π-calculi . . . . . . . . . . . . . . . 54

3.4.1 Prompts and control-specific full abstraction . . . . . . . . 55 3.5 Flattening transforms . . . . . . . . . . . . . . . . . . . . . . . . . 57

3.5.1 Flattening applications . . . . . . . . . . . . . . . . . . . . 58 3.5.2 Flattening tuples . . . . . . . . . . . . . . . . . . . . . . . 58

3.6 A duality on CPS terms . . . . . . . . . . . . . . . . . . . . . . . 59 3.7 Two connections between call-by-value and call-by-name . . . . . 62 3.8 From flattening tuples to premonoidal categories . . . . . . . . . . 64

Chapter 4 ⊗¬-categories 67 4.1 Introduction: what structure do we need? . . . . . . . . . . . . . 67 4.2 Semantics of environments in a premonoidal category . . . . . . . 69 4.3 Continuation types as higher-order structure . . . . . . . . . . . . 72 4.4 Some interdependencies of properties . . . . . . . . . . . . . . . . 75 4.5 λ-abstraction in a ⊗¬-category . . . . . . . . . . . . . . . . . . . 77

Chapter 5 The CPS term model 81

5.1 Building a category from CPS terms . . . . . . . . . . . . . . . . 81 5.1.1 First-order structure . . . . . . . . . . . . . . . . . . . . . 81 5.1.2 Application as double negation elimination . . . . . . . . . 82 5.1.3 Thunking as double negation introduction . . . . . . . . . 84

5.2 The ⊗¬ term model . . . . . . . . . . . . . . . . . . . . . . . . . 84 5.3 The indexed ¬ term model . . . . . . . . . . . . . . . . . . . . . . 92 5.4 Recursion in CPS . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

5.4.1 Recursion from iteration . . . . . . . . . . . . . . . . . . . 95

Chapter 6 Effects in the presence of first-class continuations 96

6.1 Using the current continuation twice . . . . . . . . . . . . . . . . 97

6.1.1 Writing twicecc compositionally . . . . . . . . . . . . . . 97 6.2 Copying and discarding . . . . . . . . . . . . . . . . . . . . . . . . 100

6.2.1 twicecc is not thunkable . . . . . . . . . . . . . . . . . . . 103 6.2.2 Cancellable and copyable are orthogonal . . . . . . . . . . 103 6.2.3 First-class control is not an idempotent effect . . . . . . . 104

6.3 Centrality and effect-freeness . . . . . . . . . . . . . . . . . . . . . 105

6.3.1 twicecc is not central . . . . . . . . . . . . . . . . . . . . 108 6.4 Another non-copyability result . . . . . . . . . . . . . . . . . . . . 111 6.5 The failure of Laird’s bootstrapping of force . . . . . . . . . . . . 115 6.6 Cross reference to preceding chapters . . . . . . . . . . . . . . . . 118 6.7 Discriminating λx.xx and λx.x(λy.xy) under call by name …. 118 Chapter 7 Categorical semantics in ⊗¬-categories 124

7.1 Call-by-value semantics . . . . . . . . . . . . . . . . . . . . . . . . 124

7.1.1 The naturality of callcc . . . . . . . . . . . . . . . . . . . 125 7.2 Plotkin call-by-name semantics and variants . . . . . . . . . . . . 125 7.3 Uncurrying call-by-name semantics . . . . . . . . . . . . . . . . . 127 7.4 State and meta-continuation-passing . . . . . . . . . . . . . . . . 128 7.5 Categorical semantics for CPS calculus . . . . . . . . . . . . . . . 129

7.5.1 Continuation Grabbing Style semantics for CPS-calculus . 129 7.5.2 Back to Direct Style semantics for CPS-calculus . . . . . . 130

Chapter 8 Indexed ¬-categories 135 8.1 Environments as indices . . . . . . . . . . . . . . . . . . . . . . . 135 8.2 Premonoidal categories . . . . . . . . . . . . . . . . . . . . . . . . 137 8.3 κ-categories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 8.4 Continuation semantics in indexed ¬-categories . . . . . . . . . . 143 8.5 Relating ⊗¬-categories and indexed ¬-categories . . . . . . . . . . 146

Chapter 9 Towards a graphical representation of CPS 150 9.1 A graphical calculus . . . . . . . . . . . . . . . . . . . . . . . . . 150 9.2 Duality, or inside out . . . . . . . . . . . . . . . . . . . . . . . . . 151 9.3 The CPS monoid . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 9.4 Self-adjointness, or upside down . . . . . . . . . . . . . . . . . . . 153 9.5 A semantics for linear unary CPS calculus . . . . . . . . . . . . . 155 9.6 Duality and degeneracy . . . . . . . . . . . . . . . . . . . . . . . . 156

Chapter 10 Conclusions and directions for further research 158 10.1 Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 10.2 Directions for further work . . . . . . . . . . . . . . . . . . . . . . 158

10.2.1 Language design . . . . . . . . . . . . . . . . . . . . . . . 158 10.2.2 Applications to programming . . . . . . . . . . . . . . . . 159 10.2.3 Relation to π- and related calculi . . . . . . . . . . . . . . 160 10.2.4 The expressive power of callcc . . . . . . . . . . . . . . . 161 10.2.5 Internal languages . . . . . . . . . . . . . . . . . . . . . . 161 10.2.6 Robustness . . . . . . . . . . . . . . . . . . . . . . . . . . 161 10.2.7 Refinement of the standard model . . . . . . . . . . . . . . 162 10.2.8 Relation to polymorphism and semantics in general …. 162

Bibliography 164

List of Programs

1.1 Two jumps in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.2 Two jumps in ML . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.3 Two jumps in Scheme . . . . . . . . . . . . . . . . . . . . . . . . 8 1.4 remberuptolast in ML . . . . . . . . . . . . . . . . . . . . . . . 9 1.5 remberuptolast without consing in ML . . . . . . . . . . . . . . 10 1.6 remberuptolast without consing in Scheme . . . . . . . . . . . . 10 1.7 remberuptolast with dragging a pointer across the list (In ML) . 12 1.8 remberuptolast with dragging a pointer across the list (In Scheme) 12 1.9 remberuptolast (without consing) with ML exceptions . . . . . 13 1.10 remberuptolast with explicit passing of a continuation parameter 13 1.11 remberuptolast with explicit passing of a continuation parameter 13 1.12 Categorical combinators for continuations in nj-sml . . . . . . . . 19 1.13 Categorical combinators for continuations in Scheme . . . . . . . 20

6.1 twicecc in continuation-grabbing style (nj-sml) . . . . . . . . . 98 6.2 twicecc in continuation-grabbing style (Scheme) . . . . . . . . . 98 6.3 twicecc in compositional style (nj-sml) . . . . . . . . . . . . . . 98 6.4 twicecc in compositional style (Scheme) . . . . . . . . . . . . . . 98 6.5 Effectfulness of twicecc. Copying a computation, copying its re-

sult and a context to distinguish them (nj-sml) . . . . . . . . . . 101 6.6 Effectfulness of twicecc. Copying a computation, copying its re-

sult and a context to distinguish them (Scheme) . . . . . . . . . . 101 6.7 force is not copyable (nj-sml) . . . . . . . . . . . . . . . . . . . 102 6.8 force = call/cc is not copyable (Scheme) . . . . . . . . . . . . . 102 6.9 force can reify by being precomposed (in ML) . . . . . . . . . . . 106 6.10 force can reify by being precomposed (in Scheme) . . . . . . . . . 107 6.11 twicecc is not central (shown using I/O) . . . . . . . . . . . . . 109 6.12 twicecc is not central (In ML) . . . . . . . . . . . . . . . . . . . 109 6.13 twicecc is not central (In Scheme) . . . . . . . . . . . . . . . . . 110 6.14 argfc cannot be copied (in ML) . . . . . . . . . . . . . . . . . . . 112 6.15 argfc cannot be copied (in Scheme) . . . . . . . . . . . . . . . . . 113 6.16 argfc with local state (in ML) . . . . . . . . . . . . . . . . . . . . 114 6.17 argfc with local state (in Scheme) . . . . . . . . . . . . . . . . . 114 6.18 Variant of callcc with void-returning continuations . . . . . . . . 115 6.19 Laird’s bootstrap in ML . . . . . . . . . . . . . . . . . . . . . . . 116 6.20 Laird’s bootstrap in Scheme . . . . . . . . . . . . . . . . . . . . . 116 6.21 Failure of Laird’s bootstrap: A distinguishing context in ML … 117 6.22 Failure of Laird’s bootstrap: A distinguishing context in Scheme . 117 6.23 Distinguishing λx.xx and λx.x(λy.xy) under call by name …. 122

Chapter 1

Introduction

The aim of this thesis is to make explicit the structure underlying continuation passing style, reifying it, so to speak, by making it less of a style and more of a structure.

There are (as yet) few programming languages that “have” continuations in the sense of possessing a language construct for giving unrestricted access to continuations. In a wider sense, however, most programming languages “have” continuations in some sense or another. In contemporary Computer Science, continuations may appear in various settings ^{1} and under different guises, among them at least the following:

• as a style of semantic definition in denotational semantics, giving meaning to generalised jumps;

• as a programming technique in mostly, or even purely, functional languages

• as a programming construct in (mostly/impurely) functional languages

• as a compiling technique

Many textbooks on denotational semantics, such as [Ten91] and [Sch86], contain some material on continuations in the context of imperative languages.

As a first-class continuation primitive is part of the official definition of Scheme [Re91], textbooks on Scheme, such as [FF96] typically give some examples of its use, the most advanced being perhaps [SF89].

The functional programming textbook [Hen87] gives a thorough introduction to the use of continuations in program transformation and code generation (interestingly, using a purely functional language without control operators — which can be seen as evidence of the usefulness of continuations as a technique even without language support.)

1We have listed here only what can be considered mainstream in that it appeared in several textbooks and is tought in undergraduate or at least MSc courses.

Continuations as an implementation technique are used in [FWH92] for a toy interpreter and in [App92] for the New Jersey ML compiler.

1.1 An introduction to continuations in programming languages

This section is intended to provide some background: the reader familiar with continuations can safely skip it and jump to section 1.2 below.

The goto familiar from typical imperative (or “heritage”) languages like C corresponds to a command continuation [SW74]. The much more powerful “jump with arguments”, which we will be concerned with, corresponds to expression continuations. Here a value is passed, or “thrown” along with the transfer of control, much like the arguments in a function call. These were written with the special forms valof and resultis in [SW74]; this construct survives in typical imperative languages only in the case when the block is a function body: in this case the result of the function is thrown by the return statement.

Incidentally, the reason, in our view, that goto may justly be considered “harmful” [Dij68] is that it is so weak. In particular, it cannot pass arguments and can be compared to GOSUB (without arguments) as a cruder and less structured counterpart of a genuine procedure call.

See figures 1.1, 1.2 and 1.3 on page 8 for examples of jumps with and without arguments.

We are particularly interested in two aspects of continuations: their use in giving semantics to control operators, and their use in compiling functions into more primitive jumps with arguments.

We illustrate the use of the control operator callcc by discussing a simple example, using both Scheme and (the New Jersey version of) ML in the hope that the reader may be familiar with one of these.

For Scheme, first-class continuations are part of the language definition [Re91]. In ML they are not, but the New Jersey implementation (see the manual [NJ93]) adds first-class continuations to ML by means of the following signature:

     type 'a cont
     val callcc : ('1a cont -> '1a) -> '1a
     val throw : 'a cont -> 'a -> 'b

char* f()
{
  return "Threw past the loop.\n";
  while(1);
}
main()
{
  goto skip;
  while(1);
  skip: printf("Jumped past the loop.\n");
  printf("%s", f());
}

Figure 1.1: Two jumps in C

fun loop x = loop x;
callcc(fn skip =>
       loop(throw skip ()));
output(std_out,"Jumped past the loop.\n");
output(std_out,
       callcc(fn skip =>
              loop(throw skip "Threw past the loop.\n")));

Figure 1.2: Two jumps in ML

(define (loop x) (loop x))
(begin
  (call/cc(lambda (skip)
            (loop (skip (list)))))
  (write "Jumped past the loop."))
(write
  (call/cc(lambda (skip)
            (loop (skip "Threw past the loop.")))))

Figure 1.3: Two jumps in Scheme

fun remberuptolast a lat =
callcc(fn skip =>
  let fun R [] = []
      |   R (b::l) =
          if b = a then throw skip (R l) else b::(R l)
  in
      R lat end);
             Figure 1.4: remberuptolast in ML

1.1.1 An example program

As an example of the use of expression continuations in programming, we consider the function rember-upto-last form the recent programming textbook The Seasoned Schemer [FF96]:

The function rember-upto-last takes an atom a and a lat [list of atoms] and removes all the atoms from the lat up to and including the last occurrence of a. If there are no occurrences of a, rember-upto-last returns the list.

First we transliterate ^{2} the function rember-up-to-last from the original Scheme to ML (see figure 1.4 on page 9).

remberuptolast a lat removes everything up to the last occurrence of a from the list lat. For instance:

- remberuptolast 42 [];
val it = [] : int list
- remberuptolast 42 [1,2,3,4,5,6,7,8,9];
val it = [1,2,3,4,5,6,7,8,9] : int list
- remberuptolast 42 [1,2,3,4,42,5,6,7,8,9];
val it = [5,6,7,8,9] : int list
- remberuptolast 42 [1,2,3,4,42,5,6,7,42,8,9];
val it = [8,9] : int list
- remberuptolast 42 [1,2,3,4,42,5,6,7,42,8,9,42];
val it = [] : int list

2We pass between the different lexical conventions for identifiers in ML abd Scheme (e.g.callcc and call/cc) without emphasising it. Similarly, in a more idealised setting we write λ where one would have fn in ML and lambda in Scheme.

fun remberuptolast a lat =
callcc(fn skip =>
let fun R [] = ()
      | R (b::l) =
        (R l;
         if b = a then throw skip l else ())
in
    (R lat; lat) end);

Figure 1.5: remberuptolast without consing in ML

(define (rember-upto-last a lat) ; Look Ma no cons
(call/cc
(lambda (skip)
(letrec
((R
(lambda (l)
  (if (null? l)
      (list)
      (begin
        (R (cdr l))
        (if (eq? a (car l))
            (skip (cdr l))
            (list)))))))
(begin (R lat) lat)))))

Figure 1.6: remberuptolast without consing in Scheme

The local helper function R in remberuptolast recurs over the list lat; every time the element a is encountered the remainder of the list is made the overall result by being passed to the result continuation skip. Note that this is essentially iteration and jumping, which becomes even clearer if we rewrite remberuptolast so that it does not copy the list (figures 1.5 and 1.6 for remberuptolast without consing). In this version, R does all its work by recurring and jumping, its return value being irrelevant. In that sense, it is highly non-functional, but rather “jumperative”^{3}. In fact, we can push the analogy with imperative programming even further. The solution of traversing the list and throwing every time an a in found is similar to the imperative solution of dragging a pointer across the list, that is, updating a variable every time an a is found (figures 1.7 and 1.8). In some sense, there is a duality here: a throw preempts later throws, while an assignment undoes earlier assignments. Similarly, providing lat as a default (for the case when no jump occurs) result at the very end of the argument of callcc is analogous to initialising p to lat at the very beginning.

In this example, the jump with arguments provided by continuation invocation could also be written using ML [Pau91], [MTH90] exceptions (which may be more familiar to some readers): see figure 1.9. callcc(fn skip => …) is roughly analogous to declaring a local exception and and handling it by retaining the value it passed; while while throwing a value to a continuation is analogous to raising an exception with that value.

Exceptions, and their semantic differences compared with first-class continuations, are beyond the scope of this thesis. If pushed too far, the analogy with exceptions may actually be misleading.

Hence we consider another way of explicating the role of continuations in the example: that is, by making them an explicit argument to a function. This foreshadows the formal (continuation) semantics of callcc.

Figures 1.10 and 1.11 show how remberuptolast can be written by explicitly passing a continuation parameter during the recursion of R. This yields a purely functional program, as all occurrences of control operators have been expanded out or compiled away, as it were.

Note how jumping (if an a is found in the list) in the programs with callcc in figures 1.5 and 1.6 amounts to ignoring the continuation parameter k in the programs with an explicit continuation parameter in figures 1.10 and 1.11. ^{4}

3to use a term coined by M-x dissociated-press in emacs

4Experts would perhaps point out that the versions of rember-upto-last with explicit continuations in figures 1.10 and 1.11 are not strictly the continuations counterparts of the ones with callcc and that they correspond more closely to aborting whenever an a is found and

fun remberuptolast a lat =
let val p = ref lat in
let fun R [] = ()
      | R (x::l) =
            (if x = a then p := l else ();
             R l)
in
    R lat;
    !p
end
end;

Figure 1.7: remberuptolast with dragging a pointer across the list (In ML)

(define (rember-upto-last a lat)
(letrec
((p lat)
(R
(lambda (l)
  (if (not (null? l))
      (begin
        (if (eq? a (car l))
            (set! p (cdr l)))
        (R (cdr l)))))))
(begin
(R lat)
p)))

Figure 1.8: remberuptolast with dragging a pointer across the list (In Scheme)

fun remberuptolastexn a lat =
let exception skipexn of int list in
    let fun R [] = ()
          | R (b::l) =
            (R l;
             if b = a then raise skipexn l else ())
    in
        (R lat; lat)
    end
handle skipexn x => x
end;

Figure 1.9: remberuptolast (without consing) with ML exceptions

fun remberuptolast a lat =
let fun R [] k = k ()
      | R (b::l) k =
        R l
        (fn () =>
         if b = a then l else k ())
in
    R lat (fn () =>  lat) end;

Figure 1.10: remberuptolast with explicit passing of a continuation parameter

(define (rember-upto-last a lat) ; Look Ma, no cons
(letrec
  ((R
    (lambda (l k) ; R has a continuation parameter k
      (if (null? l)
          (k)
          (R (cdr l)
             (lambda ()
               (if (eq? a (car l))
                   (cdr l)
                   (k))))))))
(R lat (lambda () lat))))

Figure 1.11: remberuptolast with explicit passing of a continuation parameter

While this formulation is very succinct, it is hard to understand in intuitive programming terms, in that what is intuitively a jump is expressed by modifying the current continuation before passing it to the recursive call. Here we have introduced continuations only in that small portion of the program that makes use of them. In general, we would have to introduce them everywhere, leading to a virtually unreadable program full of anonymous λ terms representing continuations.

1.1.2 Upward continuations

The example of rember-upto-last in section 1.1.1 is perhaps not totally felicitous in that it only uses “downward” continuations (in the sense of [FWH92]: a continuation can be passed “down”into a function call as an argument, but not “up” from it as a result). Downward continuations do not really reveal the full power of first-class continuations, as the latter comprise also the “upward” case. The last two chapters of the textbook [SF89] are devoted to the use of continuations; unfortunately heavy use is made of local state. While this combination of first-class continuations with local state gives rise to a very useful programming idiom (coroutines), it does not illustrate the power of continuations on their own, without state. It is not totally clear whether, in the absence of local state to encapsulate the current continuation [HFW86], [SF89], or at least a global queue of suspended threads [BCL^{+}96], one can obtain coroutines from continuations. See also [Shi96].

Nevertheless, the following, somewhat Mickey Mouse, example of two coroutines from [SF89] can be written without using state. Instead we use a function phi to manipulate the current continuation, about which we will say more in section 1.2 below.

     (define (phi f)
       (lambda (h)
         (call/cc (lambda (k) (h (f k))))))
     (define (ping a)
       (phi (lambda (x) (write a) x)))
     (((ping 'ping) (ping 'pong)) ((ping 'ping) (ping 'pong)))

The two functions printing ping and pong, respectively, call each other incessantly, producing a string

delimiting this within the definition of rember-upto-last.

                    pingpongpingpongpingpong . . .

In fact, an even more distilled construction of looping from a double selfapplication with first-class continuations in the untyped setting of Scheme is witnessed by the fact that

               ((call/cc call/cc) (call/cc call/cc))

loops.

The failure to consider upward continuations appears to be the cause of a misapprehension that one sometimes encounters, holding that “there are no closed terms of continuation type”. It is easy to find examples refuting this, e.g. the following:

     - callcc(fn k => throw (callcc(fn h => throw k h)) 42);
     val it = cont : int cont cont

Below, we introduce a set of functions (1.12 on 19 and figure 1.13 on page 20 for ML and Scheme) with which we can write terms of continuation type more succinctly, for instance

     - thunk 42;
     val it = cont : int cont cont

— as well as the following:

     - funtocont (fn x : int => x);
     val it = cont : (int * int cont) cont

1.1.3 Continuation Passing Style

In the last version of remberuptolast (figures 1.10 and 1.11) the function R takes an explicit continuation argument instead of seizing the current continuation by means of a callcc. More generally, a program with callcc can be translated into one without, but such that everything takes an explicit continuation argument. (This continuation argument is an ordinary function, not an element of some special continuation type.) For a highly idealised programming language, namely simply-typed λ-calculus augmented by the constant callcc, this translation is the CPS transform (adapted here from [DHM91], an extension of [Plo75]):

\overline{x} = λk.kx
\overline{λx.M} = λk.k(λxh.\overline{M}h)
\overline{MN} = λk.\overline{M}(λf.\overline{N}(λx.fxk))
\overline{callcc M} = λk.\overline{M}(λf.fkk)
\overline{throw M N} = λk.\overline{M} (λk.\overline{N}k)

CPS transforms such as this may seem quite confusing if considered formalistically as translations of λ-calculi, in that there is no obvious sense in which they are homomorphic or otherwise structure-preserving; certainly λ is not translated to λ.

This becomes somewhat clearer if we think of a CPS transform as an idealised compilation. For instance, the translation of a function λx.M needs an explicit return address h for the function body M along with its argument x; that explains λxh.\overline{M}h. As we have a language with higher-order functions, the whole function needs to be computed in the first place. Now function definitions evaluate to themselves, or more accurately, the compiled λxh.\overline{M}h is immediately passed to the overall return address k, i.e. k(λxh.\overline{M}h).

In a sense, no function in CPS ever returns; each will ultimately call some continuation. So in order to make CPS work, only some mechanism for passing arguments (without returning) is required — such as input prefixes in a process calculus. What is crucial, though, is that the recipient address of such an input can itself be passed as an argument. But that is the main feature of the π-calculus (where this is often called “mobility”).

We can thus transliterate the CPS transform above, yielding a transform with the π-calculus as the target language, related to, though not quite identical with, Milner’s translations [PS93]. (The main reason that Milner’s translation differs from our is that it was originally designed for the monadic, not the polyadic, variant of the π-calculus.) In addition, this transform also has clauses for callcc

and throw:

⦇x⦈(k) = \overline{k}⟨x⟩
⦇λx.M⦈(k) = (νl)(\overline{k}⟨l⟩ | ! l(xh).⦇M⦈(h))
⦇throw M N⦈(k) = (νm)(⦇M⦈(m) | ! m(n).⦇N⦈(n))
⦇callcc M⦈(k) = (νm)(⦇M⦈(m) | ! m(l).\overline{l}⟨kk⟩)
⦇MN⦈(k) = (νm)(⦇M⦈(m) | ! m(l).(νn)(⦇N⦈(n) | ! n(a).\overline{l}⟨ak⟩))

1.1.4 CPS as name-passing

We briefly comment on CPS as an idealised compilation, using Milner’s π-calculus as the target language.

Exponentials and λ-abstraction are often taken as foundational for the semantics of programming languages. Naively, though, a “function” call consists of two jumps with arguments: first the caller jumps to the callee, passing it the actual parameters (if any) and the return address; the callee jumps to the return address, passing the result (if any).

Less naively, one could argue that a concept of reference, address or pointer (here in particular: pointer to code) is more fundamental for actual computation than the notion of function; the π-calculus is perhaps the most successful embodiment of this view. But it is chiefly the mobility aspect of the π-calculus that matters here, concurrency being somewhat orthogonal.

Both continuation-passing style [Plo75, DHM91] and the π-calculus decompose or “compile” function abstraction into such jumps with arguments. Recall the clause for λ:

⦇λx.M⦈(k) = (νl)(\overline{k}⟨l⟩ | ! l(xh).⦇M⦈⟨h⟩)

Here the caller would have to jump to the address l with actual parameters for x and h, while the callee, for, say M = a, would jump to h with argument a. For simplicity, let us consider a function without arguments, i.e. a delayed term or thunk—the control flow becomes clearer if the jumping is not interspersed with arguments.

⦇delayM⦈(k) = (νl)(\overline{k}⟨l⟩ | ! l(h).⦇M⦈(h)h)

Such delayed terms can be forced to evaluate themselves by sending them a request for data, that is, an address where these are to be sent.

⦇force (delay M)⦈(k) = (νm) (⦇delay M⦈⟨m⟩ | m(l).\overline{l}⟨k⟩)

So in particular, for M = a,

⦇force (delay a)⦈(k) = (νm) (⦇delay a⦈(m) | ! m(l).\overline{l}⟨k⟩)
= (νm) ((νl)(\overline{m}⟨l⟩ | ! l(h).\overline{h}⟨a⟩) | ! m(l).\overline{l}⟨k⟩)
= (νl) (! l(h).\overline{h}⟨a⟩ | \overline{l}⟨k⟩)
= \overline{k}⟨a⟩
= ⦇a⦈(k)

Looking at the jumps \overline{m}⟨l⟩, \overline{l}⟨k⟩, \overline{k}⟨a⟩, we could observe that control first flows outward, as the delayed expression evaluates to the thunk located at l, then inwards, as the force sends the thunk a request to evaluate itself, and then outward again, if the forced thunk sends a. From the point of view of the delayed expression, though, one could equally say that its surrounding evaluation context is delayed—the thunk (! m(l).\overline{l}⟨k⟩) located at m— and needs to be forced by the \overline{m}⟨l⟩. The complementary forcings \overline{m}⟨l⟩ and \overline{l}⟨k⟩ cancel each other out. Somehow the computation seems to be turned around, or even to be turned inside out (twice, even). This appears to have inspired the coinage “pivots”[PS96].

In our view, this is not an epiphenomenon, but something characteristic of control flow in a mostly functional setting. In fact, we will base our account on a categorical notion of turning inside out, that is duality in the sense of adjointness of a functor to its own dual.

1.2 Introducing the continuation functor

We give a first exposition of the crucial concepts from a programming perspective.

All important examples, displayed in figures, are bilingual, in both ML and Scheme. In the main text there is usually a certain bias towards ML, largely because ML produces type information along with results.

The ML implementation used in the experiments was Standard ML of New Jersey, Version 0.93. No ML implementation with both the new value polymorphism and first-class continuations was available at the time of writing; that is why we have weak type variables ('1a, '2b, …) in programs with continuations (see [HDM93] and [NJ93]). For Scheme, Bigloo (v1.6) and Gambit were used.

A by-product of a categorical semantics is a set of so-called categorical combinators. For λ-calculus, its categorical semantics in Cartesian closed categories yields, for instance, the evaluation map and morphism pairing. These can be seen as constituting a combinatory logic, with the added benefit of being more semantically inspired than the Schönfinkel combinators S and K (see [FH88] for a discussion). It is in fact easier to define the negation functor if we know that what we are aiming for is an adjunction. Both the isomorphism of adjunction ϕ and the unit forceare easy to define, and in terms of these, we have ¬f = ϕ(f ∘ force),as in figures 1.12 and 1.13. Defined in one step, the negation functor is somewhat harder to read, not least because of the nested callcc:

fun negate f =
    fn h => callcc(fn k =>
                   throw h (f (callcc(fn p =>
                                     throw k p))));

fun force h = callcc(throw h);
    force : '1a cont cont -> '1a;
fun phi f h = callcc((throw h) o f);
    phi : ('2a cont -> 'b) -> ('b cont -> '2a);
fun negate f = phi(f o force);
    negate : ('1a -> 'b) -> ('b cont -> '1a cont);
fun thunk a = callcc(fn k => throw (force k) a);
    thunk : '1a -> '1a cont cont;
fun conttofun c a =
    callcc(fn k => throw c (a,k));
    conttofun : ('a * '2b cont) cont -> ('a -> '2b);
fun funtocont f =
    callcc((fn (a,k) => throw k (f a)) o force);
    funtocont : ('1a -> '1b) -> ('1a * '1b cont) cont;
fun delay f x = ((negate(negate f)) o thunk) x;
    delay :  ('2a -> '2b) -> ('2a -> '2b cont cont);

Figure 1.12: Categorical combinators for continuations in nj-sml

negate : ('2a -> 'b) -> ('b cont -> '2a cont);

The typing, such as it is, of continuations in Scheme consist of the single axiom

         (call-with-current-continuation procedure?) ⇒ #t

stating that what call-with-current-continuation passes to its argument is a procedure.

It is essential here that continuations do not have to be unary, that is they can take more than one argument — this makes it possible to identify functions with a special case of continuations. In ML, this can be accommodated easily, as a multi-argument function or continuation is one that takes a tuple of arguments. Moreover, this is symmetric in that multiple return values amount to a single return value that is a tuple.

Writing the same programs in Scheme is slightly awkward, because standard “R4RS” (as specified by the Revised^{4} Report on the Algorithmic Language

(define compose
  (letrec ((compose-list
            (lambda (l)
              (lambda (x)
                (if (null? l)
                    x
                    ((car l) ((compose-list (cdr l)) x)))))))
    (lambda l
      (compose-list l))))
; force (in our sense) = call/cc when applied to a continuation
(define (phi f)
  (lambda (h)
    (call/cc (compose h f))))
(define (negate f)
  (phi (compose f call/cc)))
(define (cont-to-fun c)
  (lambda (a)
    (call/cc (lambda (k)
               (c (list a k))))))
(define (fun-to-cont f)
  (call/cc
   (compose
    (lambda (ak)
      ((cadr ak) (f (car ak))))
    call/cc)))
(define (thunk a)
  (call/cc (lambda (k)
             ((call/cc k) a))))

Figure 1.13: Categorical combinators for continuations in Scheme Scheme [Re91]), does not have multiple return values. In order to return multiple values, one needs to return a single list.

Hence, unlike in ML, we cannot use even use the rudimentary pattern matching available for lambda expressions in Scheme

(lambda (x_{1} … x_{n}) M)

Instead, we write only single-argument procedures whose argument is a list. The individual arguments are extracted from this list using projections car, cadr, caddr, . . ..

In more modern implementations of Scheme incorporating multiple return values as proposed in [Ree92], one could rewrite most of the Scheme programs presented here more elegantly.

In Scheme, there is some possibility of confusion between the force as we define it and the built-in procedure force in Scheme. In fact, both have nearly the same functionality of forcing a thunk. The difference between them is that thunk forced by the Scheme force is call-by-need, in that a second forcing will not evaluate it again, whereas our force in terms of continuations only (without any updating) conforms to the original call-by-name meaning of thunks [Ing61].

1.2.1 The self-adjointness of higher-order jumping

We give a detailed, but not formalised, argument in terms of what happens during evaluation. (One could formalise this, either using a CPS transform, or the operational semantics from [HDM93].)

We would like to show that the structure that we wish to analyse is due to the intended meaning, independent of any particular formalisation.

Let callcc and throw be abbreviated as C and T, respectively.

From a programming perspective, the self-adjointness is closely related to a style of using continuations that Sabry calls Continuation Grabbing Style [Sab96].

Its characteristic idiom is the following pattern of use of callcc:

(λx.N)(C(λk.M))

where both N and M jump out of their context by ultimately throwing. We say that the callcc binds k to the continuation λx.N.

force allows the argument of a continuation to turn the tables on its continuation.

                        Th(force k) = T k h

force = λk.C(λp.T k p)

                             T h (force k)

= T h (C(λp.T k p))

T h makes h the continuation of its argument. T k h

¬(λx.x)
= λh.C(λk.T h((λx.x)force k))
= λh.C(λk.T h((λx.x)(C(λp.T k p))))
= λh.C(λk.T h(C(λp.T k p)))
= λh.C(λk.T k h)

¬f ∘ ¬g
= (λh_{1}.C(λk_{1}.T h_{1} f (C(λp_{1}.T k_{1} p_{1}))))(λh_{2}.C(λk_{2}.T h_{2} g (C(λp_{2}.T k_{2} p_{2}))))
= λh_{2}.(λh_{1}.C(λk_{1}.T h_{1} f (C(λp_{1}.T k_{1} p_{1}))))(C(λk_{2}.T h_{2} g (C(λp_{2}.T k_{2} p_{2}))))

(λh_{1}….) is a λ-expression, so its argument is evaluated next. The callcc binds the k_{2} to λh_{1}…. and T g makes the argument position of g the continuation of the following term; this seizes its current continuation and binds it to p_{2}, which is then thrown to k_{2}. Because k_{2} was bound to λh_{1}…. it follows that h_{1} becomes p_{2}. The C(λk_{1}….) seizes the overall continuation and binds k_{1} to it. T h_{1} makes h_{1} the continuation for f. Hence if f returns a result, this will be fed to h_{2} and thus to g. The argument position of f is seized by the C(λp_{1}….), and bound to p_{1} and then thrown to k_{1}.

What is important here is that the throwing in the middle amounts just to a function composition of f and g, so the whole term is equivalent to

λh.C(λk.T h (g ∘ f)(C(λp.T k p)))

which is ¬(g ∘ f).

\xymatrix{¬¬A \ar[d]_{¬¬f} \ar[r]^{force} & A \ar[d]^{f} & ¬A \ar[r]^{¬force} \ar[dr]_{id} & ¬¬¬A \ar[d]^{force} \\ ¬¬B \ar[r]^{force} & B &  & ¬A}

For example

     - 1+callcc(fn k => 10 + throw ((force o (negate force)) k) 2);
     val it = 3 : int

force = λh.C(λk.T h k)
¬ f = λh.C(λk.T h (f (force k)))
= λh.C(λk.T h (f (C(λp.T k p))))

      force ∘ ¬force

= (λh_{1}.C(λk_{1}.T h_{1} k_{1}))

∘(λh_{2}.C(λk_{2}.T h_{2} (force (C(λp.T k_{2} p)))))

= (λh_{1}.C(λk_{1}.T h_{1} k_{1}))

∘(λh_{2}.C(λk_{2}.T h_{2} ((λh_{3}.C(λk_{3}.T h_{3} k_{3})) (C(λp.T k_{2} p)))))

= λh_{2}.(λh_{1}.C(λk_{1}.T h_{1} k_{1}))(C(λk_{2}.T h_{2} ((λh_{3}.C(λk_{3}.T h_{3} k_{3})) (C(λp.T k_{2} p)))))

If we try to formulate the triangular identity in prose, we would arrive at the following narrative about jumping:

Let us assume that force ∘ ¬force gets evaluated. h_{2} becomes the current argument; let us call the overall continuation of the whole expression k. We would like to show that all that happens is that, in some circuitous manner, the current argument h_{2} is passed to the current continuation k.

First of all, the term in the operator position is evaluated; as it is a λexpression (λh_{1}….), its argument is evaluated next. This is has C in the operator expression: evaluating C(λk_{2}….) it binds k_{1} to λh_{1}. . . .. Then the throw to h_{2} is executed, making h_{2} the continuation of the subsequent term. This is an application, with a λ-expression (λ.k_{3}….) in the operator position, so its argument is evaluated next. Again, executing the C(λp. …) binds p to λh_{3}…., before throwing it to k_{2}. Now k_{2} was bound to λh_{1}, so h_{1} becomes p. The body of the λ-expression then executes the callcc in C(λk_{1}….), which binds k_{1} to the current continuation, which at this point is the overall continuation k. This is then thrown to h_{1}. This having been bound to p, which in turn points to λ.h_{3} …, h_{3} becomes k. The body of the λ-expression following λh_{3} is then evaluated; this is the fourth and last callcc. This C(λk_{3}….) binds k_{3} to the current continuation, which, due to the surrounding T h_{2}, is just h_{2}. Finally, this is thrown to k_{3}, which is to say to the overall continuation k.

Now we turn to the naturality.

                            f ∘ force

= λh.f(C(λk.T h k))

The thunk h is forced and the result supplied to f as its argument.

      force ∘ ¬¬f

= (λh_{1}.C(λk_{1}.T h_{1} k_{1}))
∘ (λh_{2}.C(λk_{2}.T h_{2} ((λh_{3}.C(λk_{3}.T h_{3} (f (force k_{3}))) (force k_{2})))
= (λh_{1}.C(λk_{1}.T h_{1} k_{1}))

∘ (λh_{2}.C(λk_{2}.T h_{2} ((λh_{3}.C(λk_{3}.T h_{3} (f (C(λp_{3}.T k_{3} p_{3}))))) (C(λp_{2}.T k_{2} p_{2})))))

= λh_{2}.(λh_{1}.C(λk_{1}.T h_{1} k_{1}))
(C(λk_{2}.T h_{2} ((λh_{3}.C(λk_{3}.T h_{3} (f (C(λp_{3}.T k_{3} p_{3}))))) (C(λp_{2}.T k_{2} p_{2})))))

h_{2} is the overall argument. The operator position is (λh_{2}….); hence the argument is evaluated. This is a callcc, which binds k_{2} to λh_{1}. . . .. The T h_{2} is executed, making h_{2} the continuation of the following term. The operator is (λk_{3}….), so the argument is evaluated. This is C(λp_{2}….), which binds p_{2} to λk_{3}. . . .. The T k_{2} is executed; because k_{2} was bound to λh_{1}…., it follows that h_{1} becomes p_{2}. The body of λh_{1}…. is evaluated; C(λk_{1}….) binds k_{1} to the overall continuation k. This is then passed to h_{1}. Because h_{1} was bound to p and p to λh_{3}…., this means that h_{3} becomes k. The C(λk_{3}….) binds k_{3} to the current continuation at this point. Because of the surrounding T h_{2} this is h_{2}.

Hence the whole term is equivalent to

λh_{2}.f(λp_{3}.T h_{2} p_{3})

Not only do first-class continuations give rise to an adjunction; this is also a particularly simple kind of adjunction. Whereas one would normally have two functors, two naturality squares (one each for unit and counit) and two triangular identities comprising an adjunction (as in a Cartesian closed category, say), we have one of each. (This is fair enough somehow, in that a continuation is half a function.)

1.2.2 Alternative control operators

We explain that our categorical combinators give a complete set of control operators, and hence an alternative to callcc and throw.

In the previous section, we focussed on the functor and the unit. But an adjunction can equally well be expressed by the isomorphisms of adjunction; we now explain how this can be seen as a new control operator.

phi together with a coercion function from functions to continuations is a complete set of control operators, like callcc together with its coercion function throw (coercing ¬τ to τ → α).

callcc does two conceptually quite separate things with the current continuation: first it copies it, then it makes one of the copies available to its argument as an ordinary function argument. The other copy is given as the current continuation to the argument of callcc.

One could separate these; in particular Felleisen’s control operator C does not copy the continuation. The continuation is given as an argument to the argument of C, but the current continuation is not supplied to it.

Like C, phi considered as a control operator does not copy the continuation, without the need to consider terms that can do without the current continuation.

For comparison, we list the CPS semantics of

• callcc with ML-style typing;

• a variant call/cc closer to that of Scheme, in that the continuation is wrapped into a procedure;

• the C-operator, which is like call/cc, but does not copy the continuation it seizes;

• ϕ, or phi in ASCII, which does not copy the continuation either, but requires a second argument to supply the continuation for its first.

\overline{callcc M} = λk.\overline{M}(λf.fkk)
\overline{call/cc M} = λk.\overline{M}(λf.f(λxk′.kx)k)
\overline{CM} = λk.\overline{M}(λf.f(λxk′.kx)(λx.x))
\overline{ϕ M N} = λk.\overline{M}(λf.\overline{N}(λn.fkn))

For comparison: callcc(λk.M) binds the current continuation, which nonetheless is also the continuation for M, to k; C(λk.M) binds the current continuation to k, the price for which is that M does not get a current continuation; ϕ(λk.M) h binds the current continuation to k and supplies h as the continuation for M.

None of these is any more generic than the others, as they are all interdefinable ^{5}, but ϕ is perhaps special in that it emphasises a certain symmetry: both callcc and throw are special instances of it.

In the typing of continuations in ML, callcc needs its companion throw, which is just a coercion from continuations to functions.

If one is willing to identify continuations with certain procedures those that ignore their result continuation), as in Scheme, then callcc on its own is enough.

If we are willing to make another identification, reducing functions to continuations, rather than embedding continuations into functions, then phi on its own, without coercions, is enough. In continuation semantics, a function call consists of passing both an argument and a return continuation to a function. A function, then, is just something that expects these two: in other words, a continuation for an argument/result continuation pair.

Hence, if σ → τ were an abbreviation for ¬(σ ∗ ¬τ), phi by itself would be sufficient. We can recover callcc and force from phi as follows.

     fun throw2 a = phi(fn h => a);
         throw2 : 'a -> 'a cont -> '2b;
     fun callcc2 f = (phi(fn k => (k,k))) (funtocont f);
         callcc2 : ('1a cont -> '1a) -> '1a;

The unit force and the negation functor can be defined similarly in terms of phi.

     val force2 = phi(fn x => x);
         force2 : '1a cont cont -> '1a;
     fun negate2 f = phi(f o force2);
         negate2 : ('1a -> 'b) -> 'b cont -> '1a cont;

Axiomatising, and calculating with, phi, force and negate would then be guided by the standard equational laws for adjunctions.

1.3 Related Work

Variants of the continuation functor, though not qua functor, have made appearances in the literature, e.g. in [Hof94]. The following from [Shi96] also appears to be related.

5We gloss over the issue of the aborting implicit in C.

     (compose-cont k f) ≡ (lambda (v) (f v k))

By the standards of Computer Science, and particularly among advanced programming language concepts, continuations are of great antiquity: the term “continuation” was coined in [SW74]; Continuation Passing Style appears implicitly in [Fis72] (final version in [Fis93]) and explicitly in [Ste78]. (For a history see [Rey93].)

They are also (explicitly or implictly) an almost ubiquitous concept. Thus the potential background to the present thesis is vast. In addition to the literature on continuations proper e.g. [Plo75, FFKD86, SF90, HDM93], work on the π-calculus ([Mil91] and [PS93]; explicitly in [Bou97]) and Scheme [Ste77, Ste78, Ste76] also has some relevance.

A “ deep” connection between continuations and classical logic in sometimes claimed, e.g. [Gri90], [RS94]. This seems orthogonal to our approach. Or we could argue that first-class continuations have plenty of interesting structure in their own right, so that there is no pressing need to establish connections to logic in order to elucidate them.

There is, however, a much smaller area of work that is of direct relevance here. The first attempt at a categorical continuation semantics was Filinski’s pioneering [Fil89]. With the benefit of hindsight, [Fil92] is in its emphasis on linearity an aberration. Filinski later chose to regard continuations not as primitive but as a special instance of monads [Fil96]. Similar in its use of the monadic metalanguage to provide a systematic presentation and classification is Danvy and Hatcliff’s [DH94]. Much can be done in that setting, but decomposing the monad into two instances of the continuation functor affords a more fine-grained analysis — including, crucially, the control operators and an abstract account of thunking [HD95].

Finally, one of the most important influences was the typing of first-class continuations in Standard ML of New Jersey, with the continuation type as primitive [HDM93]. (See also the conference version [DHM91], where a CPS semantics is given.) This type discipline is a natural starting point for a (categorical) semantics: looking for universal properties of the continuation type constructor, one is led to self-adjointness.

Incidentally, it was primarily for reasons of polymorphic typing that the continuation type was made primitive in Standard ML of New Jersey [HDM93] (whereas the more minimalist, and untyped, Scheme simply conflates continuations and procedures). We should like to regard this as a fortunate preadaption (in the Darwinian sense) on the part of ML.

To summarise, what is perhaps amazing about Continuation Passing Style is how far one can get with three little equations

\overline{x} = λk.kx
\overline{λx.M} = λk.k(λxk.\overline{M}k)
\overline{MN} = λk.\overline{M}(λm.\overline{N}(λn.mnk))

These from [Plo75], together with two more if we include callcc and throw [DHM91], encapsulate much of the backdrop to this thesis. To distill things further, we could say that the essence of the transformation is really in the clause for \overline{λx.M}, that is, what happens to a function. Much of the effort herein is devoted to trying to understand what

\overline{λx.M} = λk.k(λxk.\overline{M}k)

really means, without taking the λ’s on the right too literally, but rather adopting the view point of “λ the ultimate goto” [Ste77].

1.4 Outline

The aspect of CPS that is particularly emphasized in this thesis is that it breaks down function types into continuations.

This view of function calls as “jump with arguments” [Ste78, SS76] is not lowlevel and implementation specific, but should be taken seriously in semantics.

We also develop a calculus in support of this view. The categorical account should be seen as complementary, not as an alternative, to it. The bureaucracy inherent in names and their scope is particularly virulent in a name-passing calculus, and although the CPS calculus is in some sense like an internal language, even conceptually primitive operations can have quite complicated representations (for instance thunk and pair). This makes the more high-level, variable free, perspective of a categorical description a valuable addition.

Focussing on the category of computations also facilitates experimentation, in that we can write programs in real world languages, without some monadic interpreter. as a bag on the side of Haskell, say.

In such experiments, or validations of concepts, the categorical semantics suggests building blocks (for instance ∘ for functions, map for lists [Bac78]), which could be regarded as “categorical combinators”, like eval for the λ-calculus.

In our case, the use of these categorical combinators lets us avoid spaghetti code, like nested occurrences callcc.

Just as we try to be faithful to those features of CPS that are in evidence, such as breaking down of functions types, we avoid introducing anything that is not naturally part of it. A case in point are coproducts, in particular, the empty coproduct 0 and the identification of continuations with functions A → 0.

Parts of this thesis have appeared in [Thi96a] [Thi97]; some of it is joint work [PT97], comprising chapter 8 here.

Chapter 2 The target language of the CPS transforms is presented as a calculus in its own right, which we call the CPS calculus. This calculus is very simple and quite low-level: only variables may be passed as arguments, moreover an application is more like a jump with arguments than a λ-calculus application in that it forgets its calling context. Compared to λ-calculus, the CPS calculus could be said to be somewhere in between the λ-calculus itself and explicit substitutions. Some variants are also considered, mostly for theoretical reasons.

Chapter 3 is a review and discussion of various CPS transforms that have appeared in the literature. Call-by-value is the basic case, various other calling mechanisms being derivable by argument thunking.

Chapter 4 The categorical account of the structure underlying continuation semantics is developed. Its fundamental structure is what we call selfadjointness, i.e. a functor adjoint to its own dual in the two possible senses, i.e. on the left and on the right. Environments are modelled by means of premonoidal structure. This comes equipped with a notion of central morphism.

Chapter 5 A term model is constructed as an instance of the categorical framework in chapter 4. This is a CPS analogue of the construction of a Cartesian closed category from simply-typed λ-calculus. In the setting of the term model, the syntactic form of CPS terms can be related to the semantic properties of the morphisms they represent.

Chapter 6 is an excursion, inasmuch as it illustrates some issues concerning (semantic) notions of effect-freeness by means of concrete examples and counter-examples. Specifically, we demonstrate that a term being cancellable (which has also been called total) is not sufficient for it to be free of effects, whereas it being central is. At the same time, it is a first attempt at showing how the categorical structure of continuations can help to write programs, as we build on the functions defined in section 1.2. Thus it complements chapter 5, using the structure from chapter 4 at the level of the source language of CPS transforms.

Chapter 7 The categorical counterpart of the CPS transforms is given by categorical semantics. Parts of this chapter parallel chapter 3, giving a rational reconstruction of CPS transforms. Among the categorical structures introduced in Chapter 4, the self-adjointness on the left is shown to underlie both the semantics of control operators and the thunk/force-mechanism for variant calling strategies.

Chapter 8 A different categorical perspective on the self-adjointness is provided by studying it in the framework of indexed categories; this shows the fundamental structure for continuations to be independent of the way environments are modelled. Initially, in early drafts, there were two separate formulations of the categorical continuation semantics presented in this thesis; these were then shown to be essentially equivalent in joint work with John Power.

Chapter 9 We present some (preliminary) material on graphical representations capturing some aspects of CPS. The formal link is again established by self-adjointness — which can be visualised in this setting as turning upside down. Some issues concerning the relation of CPS to duality are raised.

Chapter 10 concludes and points towards directions for further work, among them some loose ends from the previous chapters as well as some more ambitious proposals giving continuations a fundamental rôle.

The reader interested chiefly in the programming perspective may find it useful to concentrate on section 1.2 and Chapter 6. The latter can be understood independently of most of the preceeding chapters. It is quite long, because many programs are included, but also because the counterexamples presented there, while initially intended to show only that a certain subcategory does not admit a (canonical) product, proved quite fruitful in refuting many naive assumption about continuations.

Some knowledge of category theory would probably be helpful, but only very little is really required. The various equivalent characterisations of adjunctions (found in any category theory textbook, e.g. [Mac71]) would perhaps be the most useful thing to keep in mind.

The CPS calculus

We consider the target language of CPS transforms as a calculus in its own right (similar to the intermediate language of the compiler in [App92]), which we call the CPS calculus.

The CPS calculus was first used as a common idiom for the λ- and the πcalculus in [Thi96b]. It was then turned into a calculus in its own right in the course of initial discussions with Phil Wadler and David N. Turner.

Notational preliminaries

We let lowercase letters x, y, n, m, k, l, … range over variables (names) and uppercase letters M, N, … range over terms (in various calculi). x⃗, y⃗, … range over sequences x_{1} … x_{i} of names. Commas in sequences are often omitted. When used as indices, lowercase letters range over natural numbers, e.g. x_{1} … x_{n}.

We write M[x ↦ N] for the capture-avoiding substitution of N for x in M. Similarly, if x⃗ = x_{1} … x_{j} and y⃗ = y_{1} … y_{j}, we write M[x⃗ ↦ y⃗] for the simultaneous substitution of y_{i} for x_{i} (i = 1, …, j) in M.

We use the traditional semantics brackets ⟦−⟧ for (categorical) semantics and the slightly different ⦇−⦈ parentheses for transformations that can be seen as somewhat intermediate between a proper semantics and a mere translation (a matter of degree, not principle).

2.1 CPS calculus

The raw terms of the CPS calculus are given by the following BNF:

M ::= x⟨x⃗⟩ | M{x⟨x⃗⟩=M}

We call a term of the form k⟨x⃗⟩ a jump and a term of the form M{n⟨x⃗⟩=N} a binding. As a first hint at the intended meaning, k⟨x⃗⟩ is a jump to the continuation k with actual parameters x⃗, while M{n⟨x⃗⟩=N} binds the continuation with body N and formal parameters x⃗ to n in M.

2.1.1 Remark While succinct, the presentation of the syntax is strictly speaking an abuse of notation, common for λ-calculi, in that the same symbol is used for a syntactic category and the typical metavariable ranging over it. A more technically orthodox BNF could be given as follows.

T erm ::= V ar "⟨" V ar^{∗} "⟩"
| T erm "{" V ar "⟨" V ar^{∗} "⟩=" T erm "}"

Every CPS term can be written as a jump followed by a sequence of bindings, that is, a term of the following form

k⟨x⃗⟩{p_{1}⟨y⃗_{1}⟩=M_{1}} … {p_{n}⟨y⃗_{n}⟩=M_{n}}

Hence the BNF could be written in a somewhat cluttered form, like this:

M ::= x⟨x⃗⟩{x⟨x⃗⟩=M} … {x⟨x⃗⟩=M}

The set of free variables FV(M) of a CPS term M is defined as follows.

FV(x⟨y_{1} … y_{k}⟩) = {x, y_{1}, …, y_{k}}
FV(M{n⟨y_{1} … y_{k}⟩=N}) = (FV(M) \ {n}) ∪ (FV(N) \ {y_{1}, …, y_{k}})

In M{n⟨x⃗⟩=N} the scope of n extends to the left, while that of the x_{i} extends to the right. Therefore we have left and right α-conversions.

\begin{aligned}M\{n⟨x⃗⟩=N\} & = & M\{n⟨y⃗⟩=N[x⃗ ↦ y⃗]\} & (αR) \\ M\{n⟨x⃗⟩=N\} & = & M[n ↦ n′]\{n′⟨x⃗⟩=N\} & (αL) \end{aligned}

We usually gloss over the α-conversion by identifying terms up to renaming of bound variables.

The axioms of CPS calculus are as follows.

L{m⟨x⃗⟩=M}{n⟨y⃗⟩=N} = L{n⟨y⃗⟩=N}{m⟨x⃗⟩=M{n⟨y⃗⟩=N}} (Distr)
m ≠ n m, x⃗ ∉ FV(N)
k⟨y⃗⟩{n⟨z⃗⟩=N} = k⟨y⃗⟩, n ∉ FV(k⟨y⃗⟩)  (GC)
n⟨y⃗⟩{n⟨z⃗⟩=N} = N[z⃗ ↦ y⃗]  (Jmp)
M{n⟨x⃗⟩=n′⟨x⃗⟩} = M[n ↦ n′]  (Eta)

The (Jmp) law is in some sense what drives the computation. By contrast, (GC) and (Distr) can be seen as “structural” laws like those of the π-calculus.

Most of these laws appear in Appel’s [App92]. See also [Ste78].

We will be concerned primarily with simply-typed CPS terms. The only type constructor is the negation type ¬( ). The BNF for (simple) types for the CPS calculus is as follows.

τ ::= ¬(τ_{1} … τ_{n}) | b

where b ranges over base types.

Terms are then typed according to these two rules:

\overline{Γ, k : ¬τ⃗, y⃗ : τ⃗ ⊢ k⟨y⃗⟩}  \frac{Γ, n : ¬τ⃗ ⊢ M Γ, y⃗ : τ⃗ ⊢ N}{Γ ⊢ M\{n⟨y⃗⟩=N\}}

Typing judgements in CPS are one-sided. Whereas in λ-calculus a judgement Γ ⊢_{λ} M : τ (where we have decorated the Urteilsstrich ⊢ with a λ to emphasise that this is a λ-calculus judgement) states that, in the type environment Γ, the term M has the type τ, a CPS typing judgement Γ ⊢ M states that, in the type environment Γ the CPS term is consistent. Similar type systems exist for process calculi, e.g. [Tur95]. For example, x : τ ⊢_{λ} x : τ states that under the assumption that x has type τ, x has type τ. By contrast x : τ, k : ¬τ ⊢ k⟨x⟩, states that, under the assumption that x has type τ and k has type ¬τ, passing x to τ “does not go wrong”.

Alternatively, one could compare a CPS term to a command in languages like Idealized Algol, in that it is run for effect, not value.^{1} In that sense, a CPS judgement x_{1}, …, x_{n} ⊢ M is analogous to M being a command, as in

x_{1} : var[τ_{1}], …, x_{n} : var[τ_{n}] ⊢ M : comm

We could call this “consistency” in that it implies that all internal communications channels, so to speak, are used in a consistent manner. Logically it would appear closer to inconsistency, inasmuch as Γ ⊢ M could be read as “M witnesses that Γ entails a contradiction”, such as in the example above where Γ contained both the assumptions that τ and not τ. The typing rule for the binding construct could then be read as stating that if both “Γ and τ⃗” and “Γ and not τ⃗” entail a contradiction, then the contradiction must be due to Γ alone.

In addition to these rules, we assume permutation, contraction and weakening of typing environments unless explicitly stated otherwise.

\frac{x_{1} : τ_{1}, …, x_{n} : τ_{n} ⊢ M}{x_{π(1)} : τ_{π(1)}, …, x_{π(n)} : τ_{π(n)} ⊢ M}  π is a permutation of \{1, …, n\}
\frac{Γ ⊢ M}{Γ, x : τ ⊢ M}  \frac{Γ, x : τ⃗, y : τ⃗ ⊢ M}{Γ, x : τ⃗ ⊢ M[y ↦ x]}

1This was pointed out to me by Peter O’Hearn.

One could visualize a CPS term as representing the state of a (stackless, heap-allocating) abstract machine.

\underbrace{q}_{IP}⟨\underbrace{p_{1} … p_{l}}_{actuals}⟩\{ \underbrace{n_{1}}_{address}⟨\underbrace{x_{1} … x_{i}}_{formals}⟩= \underbrace{N_{1}}_{code}\} … \{n_{m}⟨x⃗_{m}⟩=N_{m}\}

2.2 Recursive CPS calculus

All that is needed in order to make the calculus recursive is to change the visibility of names, making the address of a binding visible within its body, so that in M{n⟨x⃗⟩⇐N}, N may refer to itself under n.

We use a slightly different notation for the binding construct, “⇐” instead of “=”, to indicate the possibility of recursion.

M ::= x⟨x⃗⟩ | M{x⟨x⃗⟩⇐M}

Again, the BNF could be rendered in a more orthodox fashion:

T erm ::= V ar "⟨" V ar^{∗} "⟩"
| T erm "{" V ar "⟨" V ar^{∗} "⟩⇐" T erm "}"

For the recursive CPS calculus, we modify the typing as follows.

\frac{Γ, n : ¬τ⃗ ⊢ M Γ, x⃗ : τ⃗, n : ¬τ⃗ ⊢ N}{Γ ⊢ M\{n⟨x⃗⟩⇐N\}} Cyclic Closure

As we have broadened the scope of n, we need to modify the left α-conversion correspondingly.

M{n⟨y⃗⟩⇐N} ≡ M[n ↦ n′]{n′⟨y⃗⟩⇐N[n ↦ n′]}

The definition of free variables needs to be modified similarly.

FV(x⟨y_{1} … y_{k}⟩) = {x, y_{1}, …, y_{k}}
FV(M{n⟨y_{1} … y_{k}⟩⇐N}) = (FV(M) \ {n}) ∪ (FV(N) \ {n, y_{1}, …, y_{k}})

The set of bound variables is defined as follows.

BV(x⟨y_{1} … y_{k}⟩) = ∅
BV(M{n⟨y_{1} … y_{k}⟩⇐N}) = BV(M) ∪ BV(N) ∪ {n, y_{1}, …, y_{k}})

The axioms of the recursive CPS-calculus are as follows.

L{m⟨x⃗⟩⇐M}{n⟨y⃗⟩⇐N} = L{n⟨y⃗⟩⇐N}{m⟨x⃗⟩⇐M{n⟨y⃗⟩⇐N}} (Distr)
m ≠ n m, x⃗ ∉ FV(N)
k⟨y⃗⟩{n⟨z⃗⟩⇐N} = k⟨y⃗⟩, n ∉ FV(k⟨y⃗⟩)  (GC)
n⟨y⃗⟩{n⟨z⃗⟩⇐N} = N[z⃗ ↦ y⃗]{n⟨z⃗⟩⇐N}  (RecJmp)
M{n⟨x⃗⟩⇐n′⟨x⃗⟩} = M[n ↦ n′]  (RecEta)
n ≠ n′

Notice that the modification of the closure typing law makes the unrestricted Eta law unsound. Consider

n⟨a⟩{x⟨x⟩⇐n⟨x⟩} = n⟨a⟩[n ↦ n] = n⟨a⟩.

But we know that n⟨a⟩{x⟨x⟩⇐n⟨x⟩} loops, which should not be identified with the terminating n⟨a⟩. Hence the side condition on variables precluding the looping.

2.3 Operational semantics for CPS

We consider jumping as the only behaviour of CPS terms. The jumping axiom is accompanied by the distributive law, which can be seen as a structural congruence (in the sense of Milner) or “heating” (in the chemical metaphors of Boudol), in that its purpose is to bring together the component parts of a (jumping) redex.

2.3.1 Definition We define oriented versions of the axioms distr and jump as follows (avoiding name capture):

\begin{matrix}L\{m⟨x⃗⟩=M\}\{n⟨y⃗⟩=N\} & ⇀ & L\{n⟨y⃗⟩=N\}\{m⟨x⃗⟩=M\{n⟨y⃗⟩=N\}\} & , n ≠ m \\ n⟨y⃗⟩\{n⟨z⃗⟩=N\} & → & N[z⃗ ↦ y⃗] \end{matrix}

2.3.1 Observational congruence

While derivability from the CPS axiom is the least equality we would wish to impose on CPS terms, a notion of observational equivalence is arguably the greatest such notion we could consider. (Here least and greatest are to be understood in the sense of set-theoretic inclusion of relations, i.e. the least equality equates the fewest terms.)

We define observational equivalence ≈ for CPS. We choose at our notion of observation the “external” jump that a term may perform after it has performed some internal jumps. For instance, in a jump of the form k⟨x⃗⟩, we can observe (the occurrence of a a jump to) k. More generally, a free variable in the leftmost position can be observed.

2.3.2 Definition Let M ↓ k iff M ⇀^{∗}→^{∗} k⟨x⃗⟩{p_{1}⟨y⃗_{1}⟩=M_{1}} … {p_{n}⟨y⃗_{n}⟩=M_{n}} and k ∉ {p_{1}, …, p_{n}}.

Let M ≈ N iff for all contexts C and names k, C[M] ↓ k iff C[N] ↓ k

The axioms of the equational theory of the CPS calculus become laws in the operational congruence.

2.3.3 Proposition

L{m⟨x⃗⟩=M}{n⟨y⃗⟩=N} ≈ L{n⟨y⃗⟩=N}{m⟨x⃗⟩=M{n⟨y⃗⟩=N}} (Distr)
m ≠ n m, x⃗ ∉ FV(N)
k⟨y⃗⟩{n⟨z⃗⟩=N} ≈ k⟨y⃗⟩, n ∉ FV(k⟨y⃗⟩)  (GC)
n⟨y⃗⟩{n⟨z⃗⟩=N} ≈ N[z⃗ ↦ y⃗]  (Jmp)
M{n⟨x⃗⟩=n′⟨x⃗⟩} ≈ M[n ↦ n′]  (Eta)

This follows from preliminary work by Massimo Merro on a restricted version of the π-calculus [Davide Sangiorgi and Massimo Merro, personal communication]; the details may appear elsewhere.

2.4 Linear CPS calculus

We consider a linear version of the calculus, as linearity will sometimes allow a less complicated account.

In the linear CPS calculus the Garbage Collection and the Distributivity axioms do not make sense. For a term M{n⟨y⟩=N}, GC is applicable iff n does not occur in M, while Distr allows one to distribute the binding for n to multiple occurrences. Neither of these cases is well-typed in the linear calculus. So instead we have two separate axioms allowing us to “float” a binding into a term L{M⟨x⟩=M}, depending upon whether n occurs in the left subterm (L) or the right one (M). The linear calculus is still a fragment of the general CPS calculus, as each application of the floating laws can be simulated by distributing and garbage collection.

2.4.1 Definition The axioms of the linear CPS calculus are as follows.

n⟨y⃗⟩{n⟨z⃗⟩=N} = N[z⃗ ↦ y⃗]  (Jmp)
M{n⟨x⃗⟩=n′⟨x⃗⟩} = M[n ↦ n′]  (Eta)
L{m⟨x⃗⟩=M}{n⟨y⃗⟩=N} = L{n⟨y⃗⟩=N}{m⟨x⃗⟩=M} (Float-L)
if m ≠ n, n ∉ FV(M)
L{m⟨x⃗⟩=M}{n⟨y⃗⟩=N} = L{m⟨x⃗⟩=M{n⟨y⃗⟩=N}} (Float-R)
if m ≠ n, n ∉ FV(L)

The main point of restrictions like linearity is that they allow translations from CPS to less powerful calculi: the linear CPS calculus can be translated into Milner’s action structure for the π-calculus [Mil93]. Let k⟨x̃⟩^{†} = (ỹ)[\overline{k}⟨x̃⟩]⟨⟩. If M^{†} = (ỹn)S⟨⟩ and N^{†} = (ỹ)T ⟨⟩, then let

(M{n⟨x̃⟩=N})^{†} = (ỹ)[νn]^{⌢}S^{⌢}[n(x̃)]^{⌢}T ⟨⟩

2.4.1 Linear unary CPS calculus

But we are more interested in restricting the linear CPS calculus even further. The subset of CPS calculus in which only a single argument is allowed in jumps k⟨x⟩ and bindings M{n⟨x⟩=N} is called unary, as opposed to polyadic. The BNF for the unary subset of the CPS calculus is this:

M ::= x⟨x⟩ | M{x⟨x⟩=M}

For the fragment of CPS calculus that is both linear and unary, we can give a simplified presentation of the typing rules:

\overline{xk ⊢ k⟨x⟩}  \overline{xk ⊢ x⟨k⟩}
\frac{xn ⊢ M yk ⊢ N}{xk ⊢ M\{n⟨y⟩=N\}}  \frac{nk ⊢ M yx ⊢ N}{xk ⊢ M\{n⟨y⟩=N\}}

2.4.2 Definition The axioms of the linear unary CPS calculus are as follows.

n⟨y⟩{n⟨z⟩=N} = N[z ↦ y]  (Jmp)
M{n⟨x⃗⟩=n′⟨x⃗⟩} = M[n ↦ n′]  (Eta)
L{m⟨x⃗⟩=M}{n⟨y⟩=N} = L{n⟨y⟩=N}{m⟨x⃗⟩=M} (Float-L)
if m ≠ n, n ∉ FV(M)
L{m⟨x⃗⟩=M}{n⟨y⟩=N} = L{m⟨x⃗⟩=M{n⟨y⟩=N}} (Float-R)
if m ≠ n, n ∉ FV(L)

2.4.3 Remark In a binding expression M{n⟨x⃗⟩=N}, the bindings of N and x⃗ are conceptually quite distinct: one could try to reflect this in the calculus by letting them bind variables from different zones of the type environment.

\frac{Γ; x⃗, Δ ⊢ M}{Γ, x⃗; Δ ⊢ M}  \frac{Γ, x⃗; Δ ⊢ M}{Γ; x⃗, Δ ⊢ M}
\overline{Γ; x⃗, k ⊢ k⟨x⃗⟩}  \frac{Γ, n : ¬τ; Δ ⊢ M Γ; x⃗, Δ ⊢ N}{Γ; Δ ⊢ M\{n⟨x⃗⟩=N\}}

2.5 Constants

Although they play no rôle in the sequel, we sketch how PCF-style constants for arithmetic and conditionals could be added to the CPS calculus. Constants, like everything in CPS, take a continuation parameter.

Γ, n : int, f : ¬¬τ⃗, g : ¬¬τ⃗, k : ¬τ⃗ ⊢ ifzero⟨⌜n+1⌝fgk⟩ \overline{Γ, k : ¬int ⊢ 0⟨k⟩}

Γ, n : int, k : ¬int ⊢ succ⟨⌜n⌝ k⟩ Γ, n : int, k : ¬int ⊢ pred⟨⌜n⌝ k⟩

0⟨k⟩  = k⟨⌜0⌝⟩
succ⟨⌜n⌝ k⟩  = k⟨⌜n+1⌝⟩
pred⟨⌜n⌝ k⟩  = k⟨⌜n−1⌝⟩
ifzero⟨⌜0⌝fgk⟩  = f⟨k⟩
ifzero⟨⌜n+1⌝fgk⟩ = g⟨k⟩
diverge⟨k⟩  = p⟨⟩{p⟨⟩⇐p⟨⟩}

2.6 Translation from CPS calculus

If we think in terms of α-equivalence classes of terms, then the non-recursive CPS calculus is evidently a subset of the recursive one, as we can embed terms of the former in the latter

M{n⟨x⃗⟩=N} ↦ M{n⟨x⃗⟩⇐N}

provided we α-convert in case n is free in N to avoid its name-capturing in M{n⟨x⃗⟩⇐N}.

The recursive CPS calculus is in turn a fragment of Appel’s intermediate language, the main difference being that Appel’s FIX constructor allows mutual recursion.

CPS-calculus  Appel’s datatype cexp

          x⟨y_{1} . . . y_{j}⟩  APP(VAR x,[VAR y_{1},. . .,VAR y_{j}])

M{n⟨x_{1} … x_{j}⟩⇐N} FIX([(n,[x_{1},…,x_{j}],N)],M)

The binding of continuations in CPS can be implemented not only by “passing” (using the λ-calculus), but equally by “sending” (π-calculus) or even “grabbing” (using callcc to seize the current continuation [Sab96]).

First, the (recursive) CPS calculus can be translated into simply-typed λcalculus with a fixpoint combinator.

k⟨x_{1} … x_{n}⟩^{∘} = kx_{1} … x_{n}
(M{n⟨x⃗⟩⇐N})^{∘} = (λn.M^{∘})(μn.λx⃗.N^{∘})

Here μ is a fixpoint-finder in the simply-typed λ-calculus satisfying μx.M = M[x ↦ μx.M] and subject to the following typing

\frac{Γ, f : τ ⊢ M : τ}{Γ ⊢ μf.M : τ}

2.6.1 Proposition The translation ( )^{∘} is sound.

Proof

(n⟨x⃗⟩{n⟨y⃗⟩⇐N})^{∘}

= (λn.n x⃗)(μn.λy⃗.N^{∘})
= (λn.n x⃗)((λy⃗.N^{∘})[n ↦ μn.λy⃗.N^{∘}])
= (λy⃗.N^{∘}[n ↦ μn.λy⃗.N^{∘}]) x⃗
= N^{∘}[n ↦ μn.λy⃗.N^{∘}][y⃗ ↦ x⃗]
= N^{∘}[y⃗ ↦ x⃗][n ↦ μn.λy⃗.N^{∘}]
= (λn.N^{∘}[y⃗ ↦ x⃗])(μn.λy⃗.N^{∘})
= (λn.N[y⃗ ↦ x⃗]^{∘})(μn.λy⃗.N^{∘})
= (N[y⃗ ↦ x⃗]{n⟨y⃗⟩⇐N})^{∘}

Let n ≠ n′.

= (M{n⟨x⃗⟩⇐n′⟨x⃗⟩})^{∘}
= (λn.M^{∘})(μn.λx⃗.n′ x⃗)
= (λn.M^{∘})((λx⃗.n′ x⃗)[n ↦ μn.λx⃗.n′ x⃗])
= (λn.M^{∘})(λx⃗.n′ x⃗)
= (λn.M^{∘}) n′
= M^{∘}[n ↦ n′]
= (M[n ↦ n′])^{∘}

(M{n⟨x⃗⟩⇐N}{l⟨y⃗⟩⇐L})^{∘}

= (λl.(λn.M^{∘})(μn.λx⃗.N^{∘}))(μl.λy⃗.L^{∘})
= (λn.M^{∘})(μn.λx⃗.N^{∘}))[l ↦ μl.λy⃗.L^{∘}]
= ((λn.M^{∘})[l ↦ μl.λy⃗.L^{∘}])((μn.λx⃗.N^{∘})[l ↦ μl.λy⃗.L^{∘}])
= (((λl.M^{∘})(μl.λy⃗.L^{∘})) (((λl.N^{∘})(μl.λy⃗.L^{∘}))
= (M{l⟨y⃗⟩⇐L}{n⟨x⃗⟩⇐N{l⟨y⃗⟩⇐L}})^{∘}
□

For the non-recursive fragment of CPS calculus, one can simplify the translation to λ-calculus, not requiring the fixpoint combinator.

2.6.2 Definition

k⟨x_{1} … x_{n}⟩^{∘} = k(x_{1}, …, x_{n})
M{n⟨x_{1} … x_{n}⟩=N}^{∘} = (λn.M^{∘})(λ(x_{1}, …, x_{n}).N^{∘})

2.6.3 Remark Alternatively, we could use simply-typed λ-calculus by uncurrying the translation.

k⟨x_{1} … x_{n}⟩^{∘} = kx_{1} … x_{n}
M{n⟨x_{1} … x_{n}⟩=N}^{∘} = (λn.M^{∘})(λx_{1} … x_{n}.N^{∘})

2.6.4 Definition CPS calculus can be translated into the π-calculus as follows. (Note that continuation binding is essentially Sangiorgi’s “local environment” idiom for the π-calculus [PS96].)

k⟨x_{1} … x_{i}⟩^{•} = \overline{k}⟨x_{1} … x_{i}⟩
M{n⟨x_{1} … x_{i}⟩=N}^{•} = (νn)(M^{•} | !n(x_{1} … x_{i}).N^{•})

2.6.5 Remark In the most general π-calculus, the CPS laws are not sound. CPS gives rise to π-calculus terms of a very restricted from: there are constraints both one the occurrence of names and on the shape of terms.

On the one hand, all names are used as continuations. A precise formulation of what constitutes a continuation type discipline in the π-calculus seems to be an open problem, although a necessary (but not sufficient) constraint could be given in terms of the input/output type discipline developed in [PS96]. All names are “write-only” in that a name that has been received may be used by the receiver only for output, but not for input (it may also be passed as an argument).

Furthermore, all names are ω-receptive in the sense of [San97].

The terms in the image of the translation from the CPS calculus to the πcalculus are of a the following form, in which restriction, parallel composition, replication and input prefix occur only in an idiom and never by themselves. All outputs are asynchronous.

P, Q ::= \overline{k}⟨x_{1} … x_{n}⟩ | (νq)(P |!q(y_{1} … y_{m}).Q)

This restricted form automatically rules out usages of names like n(k).k(x).

A variant of the π-calculus that is more permissive than CPS, but still restritive enough to be “well-behaved” with regard to it is the calculus Π_{a}^{−}.

2.6.6 Definition The fragment Π_{a}^{−} of the π-calculus is given by the following

BNF: P ::= P |P | a(x⃗).P | !a(x⃗).P | (νa)P | \overline{a}⟨x⃗⟩

where in a(x_{1} … x_{n}).P none of the x_{i} appears in input position (as in x_{i}(y⃗).Q) within P.

We consider terms of Π_{a}^{−} equivalent up to barbed congruence [ACS96], written ≈_{bc}.

Hence received names can be only passed around or used for output. This together with the fact that output is asynchronous, appears to be enough to ensure a sufficiently continuation-like behaviour. More formally, we report the following result, due to Massimo Merro:

2.6.7 Proposition The translation given in definition 2.6.4 above from CPS calculus to Π_{a}^{−}/ ≈_{bc} is sound. For CPS terms M and N, M = N implies M^{•} ≈_{bc} N^{•}.

Another translation of CPS calculus is given by a “continuation-grabbing style” transformation similar to that in [Sab96], which transforms CPS terms back into idealised NJ-SML.

2.6.8 Definition The Continuation Grabbing Transform ( )^{‡} is defined as follows.

M{n⟨x_{1} … x_{j}⟩=N}^{‡} =^{def} (λ(x_{1}, …, x_{j}).N^{‡})(callcc (λn.M^{‡}))
k⟨x_{1} … x_{j}⟩^{‡} =^{def} throw k (x_{1}, …, x_{j})

On types, this translation is given by the identity.

2.6.9 Remark If Γ ⊢ M, then Γ ⊢ M^{‡} : β, where β is a fresh type variable.

Proof For terms of the form k⟨x_{1} … x_{j}⟩, this is trivial. Consider Γ ⊢ M{n⟨x⃗⟩=N}. Then

Γ, n : ¬τ⃗ ⊢ M and Γ, x⃗ : τ⃗ ⊢ N

By the induction hypothesis,

Γ, n : ¬τ⃗ ⊢ M^{‡} : β and Γ, x⃗ : τ⃗ ⊢ N^{‡} : β

Instantiating β to τ⃗ we have Γ, n : ¬τ⃗ ⊢ M^{‡} : τ⃗ and therefore Γ ⊢ λx⃗.M^{‡} : ¬τ⃗ → τ⃗. Because ⊢ callcc : (¬α → α) → α, we have Γ ⊢ callcc(λx⃗.M^{‡}) : τ⃗. Now Γ ⊢ λx⃗.N^{‡} : τ⃗ → β, so

Γ ⊢ (λx⃗.N^{‡})(callcc(λx⃗.M^{‡})) : β.
□

2.7 Idioms and jargon for the CPS calculus

We sometimes use jargon, mainly from programming language theory, together with some loan words from the π-calculus literature, for talking about CPS. This is not done for obfuscation, but to make the presentation more intuitive for the reader, who we assume is likely to be familiar with most of these terms. As a preparation for chapter 3, we give a brief discussion. The purpose is not to give terminology for different parts of a CPS term (hardly necessary, since the calculus is so simple), but to certain idioms and points of view on terms.

Like the π-calculus, the CPS calculus is a “name-passing” calculus: the only entities that may be substituted for variables are other variables (also called “names”):

n⟨y⃗⟩{n⟨z⃗⟩=N} = N[z⃗ ↦ y⃗]

Because of this name-paaing, a notational shortcut we shall perpetrate is the simulation of substitution by α-conversion. We rename the bound parameters before contracting a redex.

a⟨b⟩{a⟨x⟩=x⟨c⟩}
= a⟨b⟩{a⟨b⟩=b⟨c⟩}
= b⟨c⟩

This saves us from having to write substitutions.

a⟨b⟩{a⟨x⟩=x⟨c⟩}
= x⟨c⟩[x ↦ b]
= b⟨c⟩

A characteristic feature of (reductions in) the CPS calculus is a kind of leapfrogging of bindings like this:

k⟨f⟩{f⟨xk⟩=M}{k⟨f⟩=N{n⟨x⟩=f⟨xk⟩}}
= N{n⟨x⟩=f⟨xk⟩}{f⟨xk⟩=M}
= N{n⟨x⟩=M}

The same term may mean quite different things, depending on what we regard as the current continuation. (One of the themes of the the categorical framework is a development of this fact: one of the most basic operations is precisely this switch of current continuation.) In the CPS calculus, there is no notion of a current continuation as such; all continuations are equal. Nevertheless, when reading a term in a structured fashion, it is often essential to single out one of possibly many names as the current continuation. We consider two ways of doing this. One is always to look at judgements rather than raw terms; the other, more informal and ad hoc, is to use the same name, typically k, for the current continuation everywhere.

The translation of λ-terms gives the spirit of (nearly all) CPS transforms.

\overline{λx.M} = k⟨f⟩\{f⟨xk⟩=\overline{M}\}

Here we call f a pivot. This concept seems absent from the usual CPS terminology, probably because CPS is not normally presented as name-passing. So we borrowed it from Pierce and Sangiorgi [PS93]; they write about the translation of a λ-calculus term MN into the π-calculus:

The core of the protocol […] is the action on an internal channel v, by which the abstraction M comes to know its arguments. We call v a pivot. (In the lazy λ-calculus encoding, the role of the pivot names was played by the argument port names.)

Warning: Milner’s call-by-value π-calculus transformations have an additional level of indirection not present in CPS. So our usage of pivot is not completely the same as that of Sangiorgi and Pierce.

[Ing61] defines

A thunk is a piece of coding that provides an address. When executed, it leaves in some standard location (memory, accumulator, or index register, for example) the address of the variable with which it is associated.

In the present setting, a thunk is a term of the form k⟨q⟩{q⟨p⟩=M}. k⟨q⟩{q⟨p⟩=M} returns to its current continuation a “private” name q along which it is ready to receive a continuation p for M; M may then evaluate and return a result to p.

Complementary to thunking, a forcing is a jump with the current continuation as the actual parameter.

CPS transforms

The main purpose of this chapter is to review, and present in a unified notation, the various CPS transforms that have appeared in the literature (the seminal papers are [Plo75] and [DHM91] for callcc; a unified account is in [DH94]. See also [Fil96].)

There is a large literature on the typing of CPS transforms, beginning with [MW85], later with [Gri90] and in particular Murthy, e.g.. [Mur91]

The paradigmatic language that we consider as the source language for CPS transforms is a simply-typed λ-calculus, usually (but not in all cases) augmented by the control operators callcc and throw. The BNF for raw terms is this:

M ::= x | λx.M | MM | callcc M | throw M

We call this language λ+callcc; it is essentially the same as that in [DHM91].

To prevent misunderstanding, one should perhaps emphasise that this calculus is a (standard) idealisation of call-by-value programming languages like Scheme or ML and semantically very different from the simply-typed λ-calculus whose models are Cartesian closed categories, see [LS86].

We do not give an operational semantics here, as we regard the CPS transforms as its proper semantics, but the intended meaning of the calculus is that it should be a call-by-value calculus in the sense of [Plo75], where β and η laws appy only in a restricted sense.

(λx.M)V = M[x ↦ V]  (β_{V})
λy.V y = V  (η_{V})

where y is not free in M

Here V ranges over values, i.e. terms of the form V ::= x | λx.M.

The typing for first-class continuations in Standard ML of New Jersey in [DHM91] is given by that of simply-typed λ-calculus and the two rules for the continuation primitives callcc and throw.

\overline{Γ, x : τ ⊢ x : τ}  \frac{Γ, x : σ ⊢ M : τ}{Γ ⊢ λx.M : σ → τ}

\frac{Γ ⊢ M : σ → τ Γ ⊢ N : σ}{Γ ⊢ MN : τ}

\frac{Γ ⊢ M : ¬τ → τ}{Γ ⊢ callcc M : τ}  \frac{Γ ⊢ M : ¬τ Γ ⊢ N : τ}{Γ ⊢ throw M N : σ}

3.1 A survey of CPS transforms

From our point of view, the canonical CPS transform is the one from [DHM91].

3.1.1 Definition The call-by-value transform ⦇ ⦈ for λ+callcc is defined as follows.

⦇x⦈(k) = k⟨x⟩
⦇λx.M⦈(k) = k⟨f⟩{f⟨xh⟩⇐⦇M⦈(h)}
⦇MN⦈(k) = ⦇M⦈(m){m⟨f⟩⇐⦇N⦈(n){n⟨a⟩⇐f⟨ak⟩}}
⦇throw M N⦈(k) = ⦇M⦈(m){m⟨n⟩⇐⦇N⦈(n)}
⦇callcc M⦈(k) = ⦇M⦈(m){m⟨f⟩⇐f⟨kk⟩}
⦇¬τ⦈ = ¬⦇τ⦈
⦇σ → τ⦈ = ¬(⦇σ⦈ ¬⦇τ⦈)
⦇x_{1} : τ_{1}, … x_{n} : τ_{n}⦈ = ⦇τ_{1}⦈, …, ⦇τ_{n}⦈

This is almost the same as the transform used by Appel in his ML compiler [App92]. We recall here what he calls the naive version, which does not deal with exception handlers.

3.1.2 Definition We write x ↦ … for meta-level abstraction, i.e. ordinary function abstraction, not construction of a λ-term, and c(x) for the corresponding meta-level application. The following is Appel’s CPS transform (with the naive version of callcc) [App92, ch. 5].

A⦇−⦈ : Lambda → (Name → CP S) → CP S
A⦇x⦈(c) = c(x)
A⦇λx.M⦈(c) = c(f){f⟨xk⟩=A⦇M⦈(z ↦ k⟨z⟩)}
A⦇MN⦈(c) = A⦇M⦈(m ↦ A⦇N⦈(n ↦ m⟨nk⟩)){k⟨x⟩=c(x)}
A⦇callcc M⦈(c) = A⦇M⦈(m ↦ m⟨kk⟩){k⟨x⟩=c(x)}
A⦇throw M⦈(c) = A⦇M⦈(m ↦ c(f){f⟨xh⟩=m⟨x⟩})

Appel’s transform performs a certain amount of administrative reductions. Up to provable equality of the CPS calculus, the transform A⦇ ⦈ is the same as ⦇ ⦈.

3.1.3 Proposition

A⦇M⦈(c)) = ⦇M⦈(k){k⟨x⟩=c(x)}

Hence A⦇M⦈(z ↦ k⟨z⟩) = ⦇M⦈(k)

The call-by-value transform satisfies only the β_{V} law, not the full β law.

Plotkin’s call-by-name transform satisfies the full β-law. However, despite being traditionally called “call-by-name”, the transform does not satisfy the full η-law.

This is because it is “lazy” in the sense that λ-abstraction delays the evaluation of the body (sometimes called “protecting by a λ [Plo75])”. We could qualify “call-by-name” with “lazy” to distinguish this transform from alternatives, not afflicted by laziness, that satisfy the full η law. Unfortunately, the term “lazy” is sometimes used to mean call-by-need in the sense of memoisation of arguments.

3.1.4 Definition The Plotkin call-by-name CPS transform [Plo75] is defined as follows.

L⦇x⦈(k) = x⟨k⟩
L⦇λx.M⦈(k) = k⟨f⟩{f⟨xh⟩⇐L⦇M⦈(h)}
L⦇MN⦈(k) = L⦇M⦈(m){m⟨f⟩⇐f⟨nk⟩{n⟨h⟩⇐L⦇N⦈(h)}}
L⦇σ → τ⦈ = ¬(¬¬L⦇σ⦈, L⦇τ⦈)
L⦇x_{1} : τ_{1}, …, x_{n} : τ_{n}⦈ = ¬¬L⦇τ_{1}⦈, …, ¬¬L⦇τ_{n}⦈

3.1.5 Remark Note that the argument N in an application MN is not forced, but only located at n. While this may look simpler than the corresponding clause for call-by-value, we can nonetheless regard it as a special case of it.

L⦇MN⦈(k) = L⦇M⦈(m){m⟨f⟩⇐f⟨nk⟩{n⟨h⟩⇐L⦇N⦈(h)}}
= L⦇M⦈(m){m⟨f⟩⇐q⟨n⟩{n⟨h⟩⇐L⦇N⦈(h)}{q⟨n⟩⇐f⟨nk⟩}}

Written this way, the application follows the same pattern as for call-by-value,

L⦇M⦈(m){m⟨f⟩⇐ … {q⟨n⟩⇐f⟨nk⟩}}

except that the term in the argument position, represented by …, is not just L⦇N⦈(h), but L⦇N⦈(h) wrapped into a thunk:

q⟨n⟩{n⟨h⟩⇐L⦇N⦈(h)}

Note that the whole computation is wrapped into the thunk, so that L⦇N⦈(h) is not evaluated at this point. Alternatively, one could evaluate it, and wrap the result (if any) into a thunk:

L⦇N⦈(h){h⟨x⟩⇐q⟨n⟩{n⟨h⟩⇐h⟨x⟩}}

This would give a variant CPS transform L′⦇ ⦈, agreeing with L⦇ ⦈ except the clause for application, in which the result, not the computation, is thunked:

L′⦇MN⦈(k)
= L′⦇M⦈(m){m⟨f⟩⇐L′⦇N⦈(h){h⟨x⟩⇐q⟨n⟩{n⟨h⟩⇐h⟨x⟩}}{q⟨n⟩⇐f⟨nk⟩}}
= L′⦇M⦈(m){m⟨f⟩⇐L′⦇N⦈(h){h⟨x⟩⇐f⟨nk⟩{n⟨h⟩⇐h⟨x⟩}}}

Hence, to reiterate a point made in [DH94], the typing of call-by-name does not imply call-by-name behaviour.

The Plotkin call-by-name transform can be seen as a modification of the callby-value transform, given by delaying the argument in an application and forcing variables. According to this view, it is merely one of a number of possible variations arising from different choices about the point in the computation at which arguments are to be evaluated. Two other such variations, called the Reynolds and modified Reynolds CPS transform, are presented in [DH94].

3.1.6 Definition The Reynolds call-by-value CPS transform [DH94] is defined as follows.

R⦇x⦈(k) = x⟨k⟩
R⦇λx.M⦈(k) = k⟨f⟩{f⟨nh⟩=n⟨h′⟩{h′⟨a⟩=R⦇M⦈(h){x⟨h′′⟩=h′′⟨a⟩}}}
R⦇MN⦈(k) = R⦇M⦈(m){m⟨f⟩=f⟨nk⟩{n⟨h⟩=R⦇N⦈(h)}}

3.1.7 Definition The modified Reynolds call-by-value CPS transform [DH94] is defined as follows.

R′⦇x⦈(k) = k⟨x⟩
R′⦇λx.M⦈(k) = k⟨f⟩{f⟨nh⟩=n⟨h′⟩{h′⟨x⟩=R′⦇M⦈(h)}}
R′⦇MN⦈(k) = R′⦇M⦈(m){m⟨f⟩=f⟨nk⟩{n⟨h⟩=R′⦇N⦈(h)}}

The reason why the call-by-name CPS transform does not satisfy the η-law is, roughly speaking, that λ “protects” an application from being evaluated in the evaluation strategy codified by it. Hence in λx.MNx, the application is protected, whereas in MN, M is evaluated.

There are two possible ways one could try to address this discrepancy: either one could try to reduce under the λ in the case of λx.MNx; or one could avoid evaluating the M in MN.

Murthy defines what he calls a “truly” call-by-name CPS transform, which can be seen as reducing under λ.

3.1.8 Definition The Murthy call-by-name CPS transform [Mur92] is defined as follows.

M⦇x⦈(k) = x⟨k⟩
M⦇λx.M⦈(k) = M⦇M⦈(m){m⟨b⟩=k⟨f⟩{f⟨ak⟩=k⟨b⟩}}{x⟨h⟩=k⟨f⟩{f⟨ak⟩=a⟨h⟩}}
M⦇MN⦈(k) = M⦇M⦈(m){m⟨f⟩=f⟨nk⟩{n⟨h⟩=M⦇N⦈(h)}}

Unfortunately, to be as well-behaved as claimed, this transform requires a different notion of equality than provability by the CPS axioms. So in the present setting, we can not really make much use of it. What seems fascinating about this transform, though, is that returning the result and accessing the argument are in some sense two separate processes. Only when the argument is needed is a request sent to the calling context. This could mean that Murthy’s transform is especially suitable for a concurrent scenario. The appropriate notion of equality for this transform may be the observational congruence ≈ from definition 2.3.2.

The other, in some sense dual, possibility to make λx.MNx indistinguishable from MN, we have indicated, is to not to force the evaluation of M in an application MN. This is essentially what the uncurrying call-by-name CPS transform does.

3.1.9 Definition The uncurrying call-by-name CPS transform is defined as follows.

N ⦇x⦈(k) = k⟨x⟩
N ⦇λx.M⦈ = k⟨f⟩{f⟨xy⃗h⟩⇐N ⦇M⦈(m){m⟨g⟩⇐g⟨y⃗h⟩}}
N ⦇MN⦈ = N ⦇M⦈(m){m⟨f⟩⇐N ⦇N⦈(n){n⟨a⟩⇐k⟨g⟩{g⟨y⃗k⟩⇐f⟨ay⃗k⟩}}}
N ⦇τ_{1} → ⋯ → τ_{n} → b⦈ = ¬(N ⦇τ_{1}⦈, …, N ⦇τ_{n}⦈, ¬b)
N ⦇x_{1} : τ_{1}, …, x_{n} : τ_{n}⦈ = N ⦇τ_{1}⦈, …, N ⦇τ_{n}⦈

This transform is related to Filinski’s call-by-name transform in [Fil96] in that it doubly negates base types. In its name-passing presentation it appears to be new. There may be a connection to graph reduction here, inasmuch as an application with the result not being of base type does essentially nothing, other than consing the given argument onto the argument list. A comparison with the G-machine constructing an application graph [FH88] seems possible here.

3.2 A simplified notation for non-recursive CPS

For the non-recursive CPS calculus, we can present the CPS transforms in a shorthand style in which the CPS transform (\overline{M}, \underline{M} and \underline{\underline{M}}) of a term contains a free variable k for the current continuation. (This notational trick appears to be due to Phil Wadler; see [SW96].)

This may seem utterly confusing at first, to the extent that just about everything appears to be called k. But as the CPS calculus is a low-level name-passing calculus, it is virtually intractable without some technique for simplifying notation. Given that CPS terms admit a very “imperative” reading, one could add a more computational justification for always calling the current continuation k, in that we could think of k as always being the same register (current continuation pointer), but at different times during the computation. In that sense, the fact that the variable k can be recycled endlessly reflects the fact that the nonrecursive CPS calculus can only express forward jumps. One can destructively update the current continuation pointer, as one can never jump back to it.

3.2.1 Definition The call-by-value with callcc CPS transform in the shorthand notation \overline{( )} is defined as follows.

\overline{x} = k⟨x⟩
\overline{λx.M} = k⟨f⟩\{f⟨xk⟩=\overline{M}\}
\overline{MN} = \overline{M}\{k⟨f⟩=\overline{N}\{k⟨a⟩=f⟨ak⟩\}\}
\overline{callcc M} = \overline{M}\{k⟨f⟩=f⟨kk⟩\}
\overline{throw M N} = \overline{M}\{k⟨k⟩=\overline{N}\}
\overline{(M, N)} = \overline{M}\{k⟨m⟩=\overline{N}\{k⟨n⟩=k⟨mn⟩\}\}
\overline{σ → τ} = ¬(\overline{σ}¬\overline{τ})

For the Plotkin call-by-name semantics, the semantics of the control operators is somewhat tentative, as their intended meaning is not as clear as for call-by-value. The absence of implementations of call-by-name languages with control operators makes it hard to give pragmatic evidence as to what the definition of callcc should look like. Nonetheless, if we view the Plotkin call-by-name CPS transform as esssentially the same as call-by-name modulo thunking of function arguments, we can argue that call-by-name callcc should be as in call-by-value, with the only modification that the current continuation, once it has been seized, is wrapped into a thunk. This allows us to keep the same typing rule for callcc.

3.2.2 Definition The Plotkin call-by-name CPS transform in the shorthand notation \underline{( )} is defined as follows.

\underline{x} = x⟨k⟩
\underline{λx.M} = k⟨f⟩\{f⟨xk⟩=\underline{M}\}
\underline{MN} = \underline{M}\{k⟨f⟩=f⟨nk⟩\{n⟨k⟩=\underline{N}\}\}
\underline{callcc M} = \underline{M}\{k⟨f⟩=f⟨gk⟩\{g⟨p⟩=p⟨k⟩\}\}
\underline{throw M N} = \underline{M}\{k⟨k⟩=\underline{N}\}
\underline{σ → τ} = ¬(¬¬\underline{σ} ¬\underline{τ})

3.2.3 Definition The uncurrying call-by-name CPS transform in the shorthand notation \underline{\underline{( )}} is defined as follows.

\underline{\underline{x}} = k⟨x⟩
\underline{\underline{λx.M}} = k⟨f⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=g⟨y⃗k⟩\}\}
\underline{\underline{MN}} = \underline{\underline{M}}\{k⟨f⟩=\underline{\underline{N}}\{k⟨a⟩=k⟨g⟩\{g⟨y⃗k⟩=f⟨ay⃗k⟩\}\}\}
\underline{\underline{callcc M}} = k⟨f⟩\{f⟨y⃗h⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨gy⃗h⟩\{g⟨q⟩=q⟨y⃗h⟩\}\}\}
\underline{\underline{throw M N}} = k⟨f⟩\{f⟨y⃗h⟩=\underline{\underline{M}}\{k⟨k⟩=\underline{\underline{N}}\}\}
\underline{\underline{τ_{1} → … → τ_{n} → b}} = ¬(\underline{\underline{τ_{1}}} … \underline{\underline{τ_{n}}} ¬b)

One difference to CPS transforms using the λ-calculus as the target language is that there is no way to define the abort operator by using the λx.x as an aborting continuation (as in [FFKD86]). But we can define Danvy and Filinski’s meta-continuation passing style [DF92].

3.2.4 Definition For λ-calculus together with callcc, throw, the abort operator A and the control delimiter #, i.e. the following language

M ::= x | λx.M | MM | callcc M | throw M | A M | # M

the Danvy/Filinski metacontinuation transform \overline{\overline{( )}} is given as follows.

\overline{\overline{x}} = k⟨xc⟩
\overline{\overline{λx.M}} = k⟨fc⟩\{f⟨xkc⟩=\overline{\overline{M}}\}

\overline{\overline{MN}} = \overline{\overline{M}}\{k⟨fc⟩=\overline{\overline{N}}\{k⟨ac⟩=f⟨akc⟩\}\}
\overline{\overline{callcc M}} = \overline{\overline{M}}\{k⟨fc⟩=f⟨kkc⟩\}
\overline{\overline{throw M N}} = \overline{\overline{M}}\{k⟨kc⟩=\overline{\overline{N}}\}
\overline{\overline{# M}} = \overline{\overline{M}}\{k⟨xc⟩=c⟨x⟩\}\{c⟨x⟩=k⟨xc⟩\}
\overline{\overline{A M}} = \overline{\overline{M}}\{k⟨xc⟩=c⟨x⟩\}

The semantics of the composable continuation construct S is given as follows.

\overline{\overline{S M}} = \overline{\overline{M}}\{k⟨fc⟩=f⟨gkc⟩\{k⟨xc⟩=c⟨x⟩\}\{g⟨yk′c′⟩=k⟨yc⟩\{c⟨w⟩=k′⟨yc′⟩\}\}\}

3.3 Soundness of the uncurrying call-by-name CPS transform

The transform \underline{\underline{( )}} is genuinely call-by-name in that it satisfies both the unrestricted β- and η-laws. We show this for the non-recursive CPS calculus. Using the shorthand with the current continuation always being called k, we can keep track of what is happening without being swamped by too many distinct variables.

The crucial property of the uncurrying call-by-name CPS transform is that the transform of all λ-terms is thunkable. For the call-by-value CPS transform, by contrast, this would hold only for the transform of values.

3.3.1 Lemma Let L be a λ-term. Then its uncurrying call-by-name transform \underline{\underline{L}} satisfies:

\underline{\underline{L}} = k⟨q⟩\{q⟨z⃗⟩=\underline{\underline{L}}\{k⟨p⟩=p⟨z⃗⟩\}\}

Proof By induction on L. Let L = x.

k⟨q⟩\{q⟨z⃗⟩=\underline{\underline{x}}\{k⟨p⟩=p⟨z⃗⟩\}\}
= k⟨q⟩{q⟨z⃗⟩=k⟨x⟩{k⟨p⟩=p⟨z⃗⟩}}
= k⟨q⟩{q⟨z⃗⟩=x⟨z⃗⟩}
= k⟨x⟩
= \underline{\underline{x}}

Suppose L = λx.M and let the induction hypothesis hold for M.

k⟨q⟩\{q⟨z⃗⟩=\underline{\underline{λx.M}}\{k⟨p⟩=p⟨z⃗⟩\}\}
= k⟨q⟩\{q⟨z⃗⟩=k⟨f⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=g⟨y⃗k⟩\}\}\{k⟨p⟩=p⟨z⃗⟩\}\}
= k⟨q⟩\{q⟨z⃗⟩=f⟨z⃗⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=g⟨y⃗k⟩\}\}\}
= k⟨q⟩\{q⟨x ysk⟩=f⟨xy⃗k⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=g⟨y⃗k⟩\}\}\}

= k⟨q⟩\{q⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=g⟨y⃗k⟩\}\}
= \underline{\underline{λx.M}}

Suppose L = MN with M and N satisfying the induction hypothesis.

k⟨q⟩\{q⟨z⃗⟩=\underline{\underline{MN}}\{k⟨p⟩=p⟨z⃗⟩\}\}
= k⟨q⟩\{q⟨z⃗⟩=\underline{\underline{M}}\{k⟨f⟩=\underline{\underline{N}}\{k⟨x⟩=k⟨g⟩\{g⟨y⃗k⟩=f⟨xy⃗k⟩\}\}\}\{k⟨p⟩=p⟨z⃗⟩\}\}
= k⟨q⟩\{q⟨z⃗⟩=\underline{\underline{M}}\{k⟨f⟩=\underline{\underline{N}}\{k⟨x⟩=g⟨z⃗⟩\{g⟨y⃗k⟩=f⟨xy⃗k⟩\}\}\}\}
= k⟨q⟩\{q⟨y⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=\underline{\underline{N}}\{k⟨x⟩=f⟨xy⃗k⟩\}\}\}
= k⟨q⟩\{q⟨y⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=k⟨x⟩\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\}\{k⟨x⟩=f⟨xy⃗k⟩\}\}\}
= k⟨q⟩\{q⟨y⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨xy⃗k⟩\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\}\}\}
= k⟨q⟩\{q⟨y⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨xy⃗k⟩\}\}\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\}
= k⟨x⟩\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\}\{k⟨x⟩=k⟨q⟩\{q⟨y⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨xy⃗k⟩\}\}\}
= \underline{\underline{N}}\{k⟨x⟩=k⟨q⟩\{q⟨y⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨xy⃗k⟩\}\}\}
= \underline{\underline{N}}\{k⟨x⟩=k⟨q⟩\{q⟨y⃗k⟩=f⟨xy⃗k⟩\}\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨xy⃗k⟩\}\}\}
= k⟨f⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨xy⃗k⟩\}\}\{k⟨f⟩=\underline{\underline{N}}\{k⟨x⟩=k⟨q⟩\{q⟨y⃗k⟩=f⟨xy⃗k⟩\}\}\}
= \underline{\underline{M}}\{k⟨f⟩=\underline{\underline{N}}\{k⟨x⟩=k⟨q⟩\{q⟨y⃗k⟩=f⟨xy⃗k⟩\}\}\}
= \underline{\underline{MN}}
□

As a preparation for the soundness of β-reduction, we need to establish how, under the transform \underline{\underline{( )}}, substitution in the source language, λ-calculus, relates to a binding in the target, CPS calculus. Because of lemma 3.3.1, we have two equivalent views on what a substitution \underline{\underline{M[x ↦ N]}} means: we can see it as M, expecting the argument x, becoming the current continuation for N; or we can see it as M having access, via the pointer x, to the resource N, expecting its current continuation as its argument. In that sense, the uncurrying call-by-name transform provides a “pure” semantics for simply-typed λ-calculus: the denotation (transform) of everything is as good as a value.

3.3.2 Lemma

\underline{\underline{M[x ↦ N]}} = \underline{\underline{N}}\{k⟨x⟩=\underline{\underline{M}}\}

Proof By lemma 3.3.1, this is equivalent to

\underline{\underline{M[x ↦ N]}} = k⟨x⟩\{x⟨p⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\}\{k⟨x⟩=\underline{\underline{M}}\}
= \underline{\underline{M}}\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\}

If M = x, then

\underline{\underline{M}}\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\} = k⟨x⟩\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\} = \underline{\underline{N}}

If M = y ≠ x, then

\underline{\underline{M}}\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\} = k⟨y⟩\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\} = \underline{\underline{y}}

The remaining cases follow by induction using the fact that the binding for x distributes — except for the case when the scope of x has a hole because x is λ-bound:

\underline{\underline{(λx.M)[x ↦ N]}}
= k⟨f⟩\{f⟨xy⃗k⟩=M\{k⟨g⟩=g⟨y⃗k⟩\}\}\{x⟨z⃗⟩=\underline{\underline{N}}\{k⟨p⟩=p⟨z⃗⟩\}\}
= k⟨f⟩{f⟨xy⃗k⟩=M{k⟨g⟩=g⟨y⃗k⟩}}

because x is not free in k⟨f⟩{f⟨xy⃗k⟩=M{k⟨g⟩=g⟨y⃗k⟩}}. □ The η-law is sound for \underline{\underline{( )}} just as η_{V} is sound for \overline{( )}.

3.3.3 Proposition (η for \underline{\underline{( )}})

\underline{\underline{λx.Mx}} = \underline{\underline{M}}

where x ∉ FV(M).

Proof

\underline{\underline{λx.Mx}}
= k⟨f⟩\{f⟨xy⃗k⟩=\underline{\underline{Mx}}\{k⟨g⟩=g⟨y⃗k⟩\}\}
= k⟨f⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=k⟨g⟩\{g⟨y⃗k⟩=f⟨xy⃗k⟩\}\{k⟨g⟩=g⟨y⃗k⟩\}\}\}
= k⟨f⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨f⟩=f⟨xy⃗k⟩\}\}
= \underline{\underline{M}}  by lemma 3.3.1
□

3.3.4 Proposition (β for \underline{\underline{( )}})

\underline{\underline{(λx.M)N}} = \underline{\underline{M[x ↦ N]}}

Proof

\underline{\underline{(λx.M)N}}
= k⟨f⟩\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=g⟨y⃗k⟩\}\}\{k⟨f⟩=\underline{\underline{N}}\{k⟨x⟩=k⟨g⟩\{g⟨y⃗k⟩=f⟨xy⃗k⟩\}\}\}

= \underline{\underline{N}}\{k⟨x⟩=k⟨g⟩\{g⟨y⃗k⟩=f⟨xy⃗k⟩\}\}\{f⟨xy⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=g⟨y⃗k⟩\}\}
= \underline{\underline{N}}\{k⟨x⟩=k⟨g⟩\{g⟨y⃗k⟩=\underline{\underline{M}}\{k⟨g⟩=k⟨g⟩\}\}\} by lemma 3.3.1
= \underline{\underline{N}}\{k⟨x⟩=\underline{\underline{M}}\} by lemma 3.3.2
= \underline{\underline{M[x ↦ N]}}

□

The above generalises to the recursive CPS calculus, so we have the following.

3.3.5 Lemma

N ⦇M⦈(m) = k⟨q⟩{q⟨z⃗⟩⇐N ⦇M⦈(m){m⟨p⟩⇐p⟨z⃗⟩}}

3.3.6 Lemma

N ⦇M[x ↦ N]⦈(m) = N ⦇N⦈(n){n⟨x⟩⇐N ⦇M⦈(m)}

3.3.7 Proposition

N ⦇λx.Mx⦈(k) = N ⦇M⦈(k)

where x ∉ FV(M).

3.3.8 Proposition

N ⦇(λx.M)N⦈(k) = N ⦇M[x ↦ N]⦈(k)

3.4 CPS transforms to the λ- and π-calculi

The CPS transforms with the CPS calculus as the target language can be specialised if we compose them with one of the translations of CPS calculus into other calculi.

For the λ-calculus, we recover the usual presentation of CPS transforms with the λ-calculus as the target language.

It has been noted by several people, such as [Bou97] and also [Thi96b], that translations from the λ- to the π-calculus (see [Tur95] for a survey of them, together with a CPS-like typing) can be seen as CPS transforms.

3.4.1 Remark With the the λ-calculus as the target language, the call-by-value CPS transform is the following:

⦇x⦈ = λk.kx
⦇λx.M⦈ = λk.k(λ(x, h).⦇M⦈h)
⦇throw M N⦈ = λk.⦇M⦈⦇N⦈
⦇callcc M⦈ = λk.⦇M⦈(λm.m(k, k))
⦇MN⦈ = λk.(⦇M⦈(λm.⦇N⦈(λn.m(n, k))))

3.4.2 Remark With the the λ-calculus as the target language, the Plotkin callby-name CPS transform is the following:

L⦇x⦈ = λk.xk
L⦇λx.M⦈ = λk.k(λ(x, h).L⦇M⦈h)
L⦇MN⦈ = λk.(L⦇M⦈(λm.m(L⦇N⦈, k)))

3.4.3 Remark With the the π-calculus as the target language, the call-by-value CPS transform is the following:

⦇x⦈(k) = \overline{k}⟨x⟩
⦇λx.M⦈(k) = (νl)(\overline{k}⟨l⟩ | ! l(xh).⦇M⦈(h))
⦇MN⦈(k) = (νm)(⦇M⦈(m) | ! m(l).(νn)(⦇N⦈(n) | ! n(a).\overline{l}⟨ak⟩))
⦇throw M N⦈(k) = (νm)(⦇M⦈(m) | ! m(n).⦇N⦈(n)
⦇callcc M⦈(k) = (νm)(⦇M⦈(m) | ! m(l).\overline{l}⟨kk⟩)

where k, l, m, n are fresh.

3.4.4 Remark With the the π-calculus as the target language, the (lazy) callby-name CPS transform is the following:

L⦇x⦈(k) = \overline{x}⟨k⟩
L⦇λx.M⦈(k) = (νl)(\overline{k}⟨l⟩ | ! l(xh).L⦇M⦈(h)
L⦇MN⦈(k) = (νm)(L⦇M⦈(m) | ! m(l).(νn)(! n(h).L⦇N⦈(h) | \overline{l}⟨nk⟩))

3.4.1 Prompts and control-specific full abstraction

We would agree with [App92] that the intended meaning of (throw-ing to) a continuation is a “jump with arguments”. But the CPS transform into the λcalculus allows the definition of control operators that are rather unlike jumps, such as Felleisen’s abort operator A, definable as

⦇AM⦈ = λk.(⦇M⦈(λx.x))

By ignoring the continuation k, AM discards the surrounding evaluation context and jumps to the top level. But this is not the same as a jump by throw-ing to some continuation previously reified by a callcc, as the destination of the jump changes when the phrase AM is enclosed in a bigger program.

Stranger still from the point of view that continuations are like jumps, Felleisen’s prompt or control delimiter, definable as

⦇%M⦈ = λk.k(⦇M⦈(λx.x))

can intercept jumps out of M.

We would argue that there is a fundamental difference between callcc and throw on the one hand and A and % on the other, certainly from a naive and probably from an implementation point of view. callcc is often described as labelling a control state, much as one can label a command in low-level imperative languages. throw is then analogous to JMP or goto in that it jumps to such a label. But whereas the label bindings introduced by callcc can be statically determined from the program text, A refers dynamically to the top level. The prompt %, from this point of view, destructively updates all the references from within its argument to labels on the outside.

Whereas the definition of A for CPS into the λ-calculus looks innocuous, its very behaviour seems odd for π-calculus semantics. A is characterised by its discarding of the surrounding evaluation context E[ ], that is ⦇E[AM]⦈ = ⦇M⦈. But consider for instance E[ ] = (λx.y)[ ]. Consider

⦇(λx.y)(AM)⦈(k) = (νn)(! l(xk).\overline{k}⟨y⟩ | ⦇AM⦈(n) |! n(a).\overline{l}⟨ak⟩).

Here ⦇AM⦈ has no access at all to k. It is hard to see how this could ever be equal to ⦇M⦈(k). This would appear to be simply a violation of the visibility of names, reflecting the fact that the reference to the top level k that AM needs to escape is essentially dynamic, whereas π-calculus names are statically scoped.

Given that Sitaram and Felleisen [SF90] have shown that the prompt is necessary for the full-abstraction of standard CPS, it would be interesting to see whether the π-calculus semantics is a fully abstract translation. Continuation semantics in the π-calculus provides a different angle than the standard CPS model on the relative status of various control constructs. In the usual CPS semantics, direct style does not allow for control operators; and as soon as the interpretation is “bumped up” in the functional hierarchy by the CPS transform, the prompt is introduced along with callcc and throw. Felleisen and Sitaram [SF90] argue on the basis of this for the naturality of the prompt. In the π-calculus semantics, by contrast, the control operators already exist in the structure necessary to support the λ-calculus; and the prompt could be introduced only in a very imperative manner, by destructively updating the continuation of its subterm.

3.4.5 Remark We point out a connection between Milner’s original encoding of the λ- in the π-calculus and a recently discovered CPS transform [HS97] for the λμ-calculus [Par92]. In each case, functions are not fully CPS transformed. Rather, a construct in the target language not properly inside the CPS discipline is used for the translation of functions. For the π-calculus, the parameter of the translation is used for input, whereas in CPS it would only ever be used for asynchronous output. Recall Milner’s original transform [Mil91] from the λ- to the π-calculus (which he calls “encoding of the lazy λ-calculus”):

L⦇x⦈(k) = \overline{x}⟨k⟩
L⦇λx.M⦈(k) = k(xh).L⦇M⦈(h)
L⦇MN⦈(k) = (νm)(L⦇M⦈(m) | (νn)(\overline{m}⟨nk⟩ | !n(h).L⦇N⦈(h)))

The Hofmann-Streicher CPS transform ˆ( ) from the λμ-calculus is defined as follows.

x̂ = λk.xk
{ \atop λx.M} = λ(x, k).M̂k
M̂N = λk.M̂(N̂, k)
{ \atop μa.M} = λa.M̂()
{ \atop [a]M} = λ().M̂a
{ \atop σ → τ} = ¬σ̂ × τ̂
{ \atop Γ, x : τ} = Γ̂, x : ¬τ̂

Here ¬τ abbreviates τ → o. The typing invariant of ˆ( ) is: if Γ ⊢ M : τ, then Γ̂ ⊢ M̂ : ¬τ̂. (CPS transforms are usually more comprehensible if one does not η-reduce them.) From our point of view, such transforms are somewhat vexing, in that they introduce continuations in some places, but do not appear to break down function types into continuations in quite the same way that the canonical CPS transforms do. It is not clear whether they can be accommodated in our semantic framework.

3.5 Flattening transforms

We review flattening transforms, known to be a first step towards CPS; see [LD93], although our account is somewhat different.

A first-order analogue of flattening for tuples leads us to a motivation for premonoidal categories: we derive them from the computationally natural principle of naming all intermediate results.

3.5.1 Flattening applications

We recall from [LD93] that CPS transforms can be staged by first translating into a flattened form (called Core Direct Style in [LD93]); here we use a let-language similar to that in [DH94].

3.5.1 Definition (Flattening transform for λ-terms)

x^{♭} = x
(λx.M)^{♭} =^{def} λx.M^{♭}
(M N)^{♭} =^{def} let f = M^{♭} in let a = N^{♭} in f a

3.5.2 Definition The CPS transforms for flat λ-terms are defined as follows.

\overline{x} = k⟨x⟩
\overline{λx.M} = k⟨f⟩\{f⟨xk⟩=\overline{M}\}
\overline{fa} = f⟨ak⟩
\overline{let x = N in M} = \overline{N}\{k⟨x⟩=\overline{M}\}
\underline{x} = x⟨k⟩
\underline{λx.M} = k⟨f⟩\{f⟨xk⟩=\underline{M}\}
\underline{fa} = f⟨k⟩\{k⟨f⟩=f⟨ak⟩\}
\underline{let x = N in M} = \underline{M}\{x⟨k⟩=\underline{N}\}

3.5.3 Proposition The call-by-value and the lazy call-by-name CPS transforms factor over flattening.

\overline{M^{♭}} = \overline{M} \underline{M^{♭}} = \underline{M}

3.5.2 Flattening tuples

In the above, the only compound expressions (or serious terms) were combinations M N. What the flattening transform achieved was to compile λ-calculus into a de-sugared form in which the only combined expressions were those made up of variables, f a.

We will now consider a first-order analogue of this, focussing on products instead of function spaces. We de-sugar (flatten) tuple expressions (M, N) in the same way as was done for applications M N. The target language shares some features with Moggi’s metalanguage [Mog89], most notably in the distinction between values and computations, but without any reference to monads.

In this setting, we have a very restricted notion of value: tuples of variables. More complex entities cannot themselves be entries in a tuple; instead, all intermediate results are named and only the names can appear in tuples. Values are given by the following BNF:

V ::= () | x | (V, V)

Computations are values or let-expressions

M ::= V | let x = M in M

in general, though, M will range over other things in addition to the above, e.g. computations with side-effects.

3.5.4 Definition The typing of the flattened tuple language is given by the following rules.

\overline{x : τ ⊢ x : τ}  \frac{Γ ⊢ U : σ Δ ⊢ V : τ}{Γ, Δ ⊢ (U, V) : σ ⊗ τ} where U and V are values
\frac{Γ ⊢ N : σ Δ, x : σ ⊢ M : τ}{Δ, Γ ⊢ let x = N in M : τ}  \frac{Γ ⊢ N : σ x : σ, Δ ⊢ M : τ}{Γ, Δ ⊢ let x = N in M : τ}

3.5.5 Definition The tuple flattening transform is defined as follows.

x^{♭} =^{def} x
(M, N)^{♭} =^{def} let a = M^{♭} in let b = N^{♭} in (a, b)

3.6 A duality on CPS terms

We recall the call-by-value and (lazy) call-by-name CPS transforms.

\overline{x} = k⟨x⟩
\overline{λx.M} = k⟨f⟩\{f⟨xk⟩=\overline{M}\}
\overline{MN} = \overline{M}\{k⟨f⟩=\overline{N}\{k⟨x⟩=f⟨xk⟩\}\}

\underline{x} = x⟨k⟩
\underline{λx.M} = k⟨f⟩\{f⟨xk⟩=\underline{M}\}
\underline{MN} = \underline{M}\{k⟨f⟩=f⟨xk⟩\{x⟨k⟩=\underline{N}\}\}

It is striking that the translations of a free variable x under the two transforms are dual to each other in the sense that each arises from the other by swapping operator and argument. The same duality is evident in the translations of the argument N in an application MN; this is \overline{N}\{k⟨x⟩= …\} for call-by-value and … \{x⟨k⟩=\underline{N}\} for call-by-name.

As long as continuations are unary, it is easy to define a transform that swaps operator and argument everywhere. This does not make sense for general, polyadic, continuations, as one cannot have a tuple in the operator position. But inasmuch as replacing k⟨x⟩ with x⟨k⟩ amounts to replacing x with a thunk, we can define a transformation for non-unary continuations f⟨y⃗⟩ in the same spirit, by thunking them, giving f⟨q⟩{q⟨p⟩=p⟨y⃗⟩}. To compensate for this thunking, the bindings for non-unary continuations … {f⟨y⃗⟩= …} need to be translated with an additional forcing q⟨p⟩, giving … {f⟨q⟩=q⟨p⟩{p⟨y⃗⟩= …}}

3.6.1 Definition For a CPS term M, let its dual M^{⊥} be defined inductively as follows:

k⟨x⟩^{⊥} =^{def} x⟨k⟩
M{n⟨x⟩=N}^{⊥} =^{def} N^{⊥}{x⟨n⟩=M^{⊥}}
f⟨y⃗⟩^{⊥} =^{def} f⟨q⟩{q⟨p⟩=p⟨y⃗⟩}
M{f⟨y⃗⟩=N}^{⊥} =^{def} M^{⊥}{f⟨q⟩=q⟨p⟩{p⟨y⃗⟩=N^{⊥}}}

for y⃗ ≠ x; that is, y⃗ ranges over sequences other than those of unit length.

The duality between call-by-name and call-by-value is particularly vivid when we transform terms after they have been flattened.

3.6.2 Proposition For a CPS term M, M^{⊥⊥} = M

Proof By induction on M. The cases k⟨x⟩ and M{n⟨x⟩=N} are trivial. For the remaining two cases, we have

f⟨y⃗⟩^{⊥⊥}
= f⟨q⟩{q⟨p⟩=p⟨y⃗⟩}^{⊥}
= p⟨y⃗⟩^{⊥}{p⟨q⟩=q⟨f⟩}
= p⟨q⟩{q⟨f⟩=f⟨y⃗⟩}{p⟨q⟩=q⟨f⟩}
= f⟨y⃗⟩
M{f⟨y⃗⟩=N}^{⊥⊥}

= M^{⊥}{f⟨q⟩=q⟨p⟩{p⟨y⃗⟩=N^{⊥}}}^{⊥}
= p⟨q⟩{p⟨q⟩=q⟨f⟩{f⟨y⃗⟩=N^{⊥⊥}}}{q⟨f⟩=M^{⊥⊥}}
= q⟨f⟩{f⟨y⃗⟩=N^{⊥⊥}}{q⟨f⟩=M^{⊥⊥}}
= M^{⊥⊥}{f⟨y⃗⟩=N^{⊥⊥}}
= M{f⟨y⃗⟩=N} by the induction hypothesis

□

This is a duality between call-by-value and call-by-name in the sense that it connects the corresponding CPS transforms.

3.6.3 Proposition For a λ-term M not containing control operators,

\overline{M}^{⊥} = \underline{M} and \underline{M}^{⊥} = \overline{M}

Proof By induction on M. For M = x,

\overline{x}^{⊥} = k⟨x⟩^{⊥} = x⟨k⟩ = \underline{x}

And conversely. Let the induction hypothesis hold for M and N.

\overline{M N}^{⊥}
= \overline{N}\{k⟨x⟩=\overline{M}\{k⟨f⟩=f⟨xk⟩\}\}^{⊥}
= f⟨xk⟩^{⊥}\{f⟨k⟩=\overline{M}^{⊥}\}\{x⟨k⟩=\overline{N}^{⊥}\}
= f⟨q⟩\{q⟨p⟩=p⟨xk⟩\}\{f⟨k⟩=\overline{M}^{⊥}\}\{x⟨k⟩=\overline{N}^{⊥}\}
= \overline{M}^{⊥}\{k⟨p⟩=p⟨xk⟩\}\{x⟨k⟩=\overline{N}^{⊥}\}
= \underline{M}\{k⟨p⟩=p⟨xk⟩\}\{x⟨k⟩=\underline{N}\}
= \underline{M N}
\underline{M N}^{⊥}
= \underline{M}\{k⟨f⟩=f⟨xk⟩\{x⟨k⟩=\underline{N}\}\}^{⊥}
= \underline{N}^{⊥}\{k⟨x⟩=f⟨q⟩\{q⟨p⟩=p⟨xk⟩\}\}\{f⟨k⟩=\underline{M}^{⊥}\}
= \underline{N}^{⊥}\{k⟨x⟩=\underline{M}^{⊥}\{k⟨p⟩=p⟨xk⟩\}\}
= \overline{N}\{k⟨x⟩=\overline{M}\{k⟨p⟩=p⟨xk⟩\}\}
= \overline{M N}
\overline{λx.M}^{⊥}
= k⟨f⟩\{f⟨xk⟩=\overline{M}\}^{⊥}

= f⟨k⟩\{f⟨q⟩=q⟨p⟩\{p⟨xk⟩=\overline{M}^{⊥}\}\}
= f⟨k⟩\{f⟨q⟩=q⟨p⟩\{p⟨xk⟩=\underline{M}\}\}
= k⟨p⟩\{p⟨xk⟩=\underline{M}\}
= \underline{λx.M}
\underline{λx.M}^{⊥}
= k⟨f⟩\{f⟨xk⟩=\underline{M}\}^{⊥}
= f⟨k⟩\{f⟨q⟩=q⟨p⟩\{p⟨xk⟩=\underline{M}^{⊥}\}\}
= k⟨p⟩\{p⟨xk⟩=\underline{M}^{⊥}\}
= k⟨p⟩\{p⟨xk⟩=\overline{M}\}
= \overline{λx.M}

□

However, the duality does not in general respect equality, that is there are M_{1} and M_{2} with M_{1} = M_{2} (provable equality), but not M_{1}^{⊥} = M_{2}^{⊥}. Even for terms arising as CPS transform of λ-terms, equality is not preserved in general. Consider (λx.y)(fg).

\overline{(λx.y)(fg)} = f⟨gx⟩\{k⟨x⟩=k⟨y⟩\}
\underline{(λx.y)(fg)} = y⟨k⟩\{k⟨x⟩=f⟨gx⟩\} = k⟨y⟩

Clearly, the Garbage Collection axiom is the culprit here, so perhaps restricting to the linear CPS calculus could help with the preservation of equality. Note that the duality is well-behaved with respect to the Jmp and Eta laws, in that it transforms their redexes into each other.

(M[n ↦ m])^{⊥} = (M{n⟨x⟩=m⟨x⟩})^{⊥} = x⟨m⟩{x⟨n⟩=M^{⊥}} = M^{⊥}[n ↦ m]

3.7 Two connections between call-by-value and call-by-name

We have already mentioned in 3.1.5 that the lazy call-by-name CPS transform can be seen as arising from thunking.

Consider λ-calculus augmented by two special forms, force and delay. We give a translation from λ-calculus into the augmented variant.

                          x^{t} = force x

λx.M^{t} = λx.M^{t}
(MN)^{t} = M^{t}(delay N^{t})

We adapt the call-by-value CPS transform for special forms force and delay as follows.

\overline{force x} = k⟨x⟩
\overline{delay M} = k⟨p⟩\{p⟨k⟩=\overline{M}\}

Then call-by-name factors over call-by-value by virtue of thunking [HD95]: \underline{M} = \overline{M^{t}} (see [HD95]).

We note that the variables appearing in a source term are reused in its CPS transform, along with continuation variables freshly generated by the CPS transform. For different transforms, this reuse is conceptually quite different.

In call-by-value, the variables from the source language are recycled to denote the value of the translated term being passed to the current continuation. The latter is anonymous, inasmuch as the current one is always used. For instance, in \overline{x} = k⟨x⟩, the variable x evaluates to itself; hence x is passed to the current continuation, which, in the shorthand version of the transform, is always called k. For \overline{λx.M} = k⟨f⟩\{f⟨xk⟩=\overline{M}\}, the λ-expression evaluates to a closure; a pointer (private name) to this is passed to the current continuation.

For call-by-name, there are two intuitively different, but equivalent readings. Either one of these may seem more natural, depending on whether one looks at the thunking or the flattening transform as an intermediate step towards call-by-name CPS. They are equivalent in that they are adjoint correspondent under the self-adjointness on the left.

In the first interpretation, which regards call-by-name as a variant of callby-value obtained by thunking, λ-calculus variables are recycled in the manner described above for call-by-value. The difference that the environment does not hold values, but thunks. Hence \underline{x} = k⟨x⟩ is read as saying that the thunk whose address x is stored in the environment is forced by being sent the current continuation k. (Categorically, then, ⟦x : τ ⊢ x : τ⟧ denotes a morphism ¬¬⟦τ⟧ ⟶ ⟦τ⟧ representing this forcing.) According to this view, the translation of λ-expressions is identical to that under call-by-value.

The second interpretation of call-by-name holds that source language variables are recycled to denote the current continuation of the translated expression. The latter, rather than the current continuation, now becomes an anonymous request channel or return address. \underline{\underline{x}} = k⟨x⟩ is read as saying that k is passed to the current continuation, called x, of the transform of the λ-term x. (Categorically, this means that ⟦x : τ ⊢ x : τ⟧ denotes a morphism ¬⟦τ⟧ ⟶ ¬⟦τ⟧ representing the identity.) Under this interpretation, application in particular is quite different from the call-by-value case.

\underline{fx} = f⟨q⟩\{q⟨p⟩=p⟨xk⟩\}

f is now the current continuation of application, to which the request channel q is passed.

3.8 From flattening tuples to premonoidal categories

One could argue that the tuple notation (M, N) in a call-by-value language should be considered as no more than syntactic sugar for the flattened form

let a = M in let b = N in (a, b)

and that semantics should be based on the de-sugared form. Thus semantics should not be based on the categorical structure for which the tuple language M ::= () | x | (M, M) is the internal language, finite products [Cro93]; but instead on the structure corresponding to the de-sugared let-language.

In the spirit of categorical semantics (for an accessible introduction, see e.g. the textbook [Cro93]), we now attempt to arrive at a categorical semantics.

The minimal setting for a semantics of the flattened tuple language is a category equipped with a “tensor”, more precisely, a binoidal category [PR97].

The let-construct is decomposed into tensor A ⊗ ( ) and composition. That is, in a judgement

Δ, Γ ⊢ let x = N in M : τ

⟦Δ⟧ is composed “in parallel” with ⟦Γ ⊢ N : σ⟧ by means of the tensor ⟦Δ⟧ ⊗ ( ), and the result is composed “sequentially” with ⟦Δ, x : σ ⊢ M : τ⟧ by means of the categorical composition “; ”.

In order to make the semantics cope with ambiguities in the notation, specifically writing environments as associative lists, we require coherence conditions that make the different readings of ambiguous syntax agree semantically. This leads to the notion of premonoidal category [PR97].

3.8.1 Definition Given a premonoidal category and an interpretation ⟦ ⟧ of base types as objects of that category, we give a semantics to the flattened tuples language as follows.

⟦x : τ_{1}, … x_{n} : τ_{n}⟧ =^{def} (… (⟦τ_{1}⟧ ⊗ …) …) ⊗ ⟦τ_{n}⟧

⟦x : τ ⊢ x : τ⟧ =^{def} id
⟦Γ, Δ ⊢ let x = N in M : τ⟧ =^{def} ⟦Γ ⊢ N : σ⟧ ⊗ ⟦Δ⟧; ⟦x : σ, Δ ⊢ M : τ⟧
⟦Δ, Γ ⊢ let x = N in M : τ⟧ =^{def} ⟦Δ⟧ ⊗ ⟦Γ ⊢ N : σ⟧; ⟦Δ, x : σ ⊢ M : τ⟧
⟦Γ, Δ ⊢ (U, V) : σ ⊗ τ⟧ =^{def} ⟦Γ ⊢ U : Γ⟧ ⊗ ⟦Δ⟧; ⟦σ⟧ ⊗ ⟦Δ ⊢ V : τ⟧
⟦x : τ_{1}, (y : τ_{2}, z : τ_{3}) ⊢ ((x, y), z) : (τ_{1} ⊗ τ_{2}) ⊗ τ_{3}⟧ =^{def} assoc
⟦(x : τ_{1}, y : τ_{2}), z : τ_{3} ⊢ (x, (y, z)) : τ_{1} ⊗ (τ_{2} ⊗ τ_{3})⟧ =^{def} assoc^{−1}

The coherence theorems [PR97] for premonoidal categories then let us write environments associatively. The ambiguities in the syntax do not lead to problems because “every diagram commutes” (as long as it is made up of the denotations of values).

The let expression can be written almost exactly the same in ML (let val x = N in M end). By way of illustration, consider the following ML code.

     fun assoc ((x,y),z) = (x,(y,z));
         assoc : ('a * 'b) * 'c -> 'a * ('b * 'c);
     fun tensorleft f (a,x) = (a, f x);
         tensorleft : ('a -> 'b) -> 'c * 'a -> 'c * 'b;
     fun tensorright f (x,a) = (f x, a);
         tensorright : ('a -> 'b) -> 'a * 'c -> 'b * 'c;

In Scheme, we would write flattened tuple expressions using let* like this: (let* ((a M) (b N)) (list (a b))). Note that the heterogeneous lists in Scheme allow us to define a strict premonoidal category by using list concatenation for defining ⊗.

Some readers may be surprised that the premonoidal structure is not in fact monoidal. We briefly illustrate why one should not expect this. For example, in a language with state, there are two possible meanings of a tuple (M, N), depending which component is evaluated first. Consider the following examples, where we make the evaluation order explict by using let.

     let val s = ref 0 in
     let val x = (s := !s + 1; !s) in

     let val y = (s := !s + 1; !s)
     in #1(x,y) end end end ;
     let val s = ref 0 in
     let val y = (s := !s + 1; !s) in
     let val x = (s := !s + 1; !s)
     in #1(x,y) end end end;

Just as for state, in the presence of continuations (first-class or otherwise) there are two possible meanings of the tuple (throw k 1, throw k 2).

     callcc(fn k =>
            let val x = throw k 1 in
            let val y = throw k 2
            in #1(x,y) end end);
     callcc(fn k =>
            let val y = throw k 2 in
            let val x = throw k 1
            in #1(x,y) end end);

In a monoidal category, there would be no way to distinguish between the two composites. This makes monoidal categories suitable for those cases where both composites are evaluated in parallel or where there can be no interference between the two (which would the case, say, if both had access to disjoint pieces of state). But with control, as given by continuations, we have both a sequential evaluation order and interference between the components, since a jump in one will prevent the other from being evaluated at all.

Put differently, the presence of computational effects, like state and control, “breaks” the bifunctoriality, so one is left with a binoidal category. (Partiality appears to be a separate case that should perhaps not be lumped together with genuine effects like state and control.)

⊗¬-categories

In this chapter, we develop a categorical account of the structure inherent in first-class continuations.

For first-class continuations, it is particularly worthwhile to look at the category of computations, for the following reasons:

• First-class continuations allow the full callcc to be added to the language, which is the most powerful version of control found in actual languages. This contrasts with the situation for state, where only a rather weak notion of global state is added by commonly used notions like the state monad.

• The construct to be studied has universal properties on the category of computations. That does not seem to be the case for constructs associated with state, such as assignment.

• Continuations are an advanced concept in programming languages that could be made easier to use by semantic clarification. While local state has subtleties, it is not obvious if global variables as introduced by the state monad are all that much in need of elucidation.

(We have made a comparsion with state here, as state and control appear to be the most natural things to add to a programming language, but this discussion would apply to other effects, e.g. exceptions.)

4.1 Introduction: what structure do we need?

The task of finding a semantic infrastructure for continuation semantics is somewhat analogous to that of interpreting λ-calculus in a cartesian closed category. We need a first-order structure for interpreting environments and tuple types, in analogy with, but weaker than, cartesian products, as well as higher-order structure for interpreting continuation types. These now become the fundamental notion, while arrow types are derived as a special instance of continuation types. But whereas in λ-calculus every morphism is a “pure function”, in CPS there is a need to identify a subcategory of effect-free computations (or values) that satisfy stronger properties than the general, possibly effectful, computations.

We show that effect-freeness in the presence of first-class continuations is a more subtle notion than would at first appear. In particular, it is not enough to exclude straightforward jumps like throw k 42.

In our framework, environments are modelled by means of a premonoidal category [PR97]: this is a categorical framework which provides enough parallelism on types to accommodate programs of multiple arity, but no real parallelism on programs. For each object (type) A, there are functors A ⊗ ( ) and ( ) ⊗ A. For morphisms f : A ⟶ A′ and g : B ⟶ B′, there are in general two distinct parallel compositions, f ⊗B; A′ ⊗g and A⊗g; f ⊗B′. The central morphisms are those f such that for all g, the above composites agree. That is, those programs phrases which are not sensitive as to whether they are evaluated before or after any other. This provides a robust notion of effect-free morphism.

The continuation type constructor extends to a contravariant functor, as every function σ → τ gives rise to a continuation transformer τ cont → σ cont in the opposite direction. This functor is adjoint to itself on the left, i.e., there is a natural bijection

\frac{¬B ⟶ C}{¬C ⟶ B}

We use the notation ¬ and refer to an application of the continuation functor more succintly as a negation, without claiming any deep connection. ^{1}

Intuitively, a morphism ¬B ⟶ C expects both a B- and a C-accepting continuation as its argument and current continuation, respectively. The above correspondence arises by simply switching these. This ties in with the typing of continuations in Standard ML of New Jersey.

We can define the unit force : ¬¬A ⟶ A of this adjunction, the isomorphism of adjunction ϕ : hom(¬B, C) ⟶ hom(¬C, B) and the negation functor itself.

We require this to hold even “parametrically” in some other object A

\frac{A ⊗ ¬B ⟶ C}{A ⊗ ¬C ⟶ B}

1There is a formal resemblance between the continuation functor and logical negation, just as there is a formal resemblance between, say, slice categories and division on the integers by virtue of C/1 ≅ C and C/A/B ≅ C/(A × B).

The unit of this adjunction is the application map apply : A ⊗ ¬(A ⊗ ¬B) ⟶ B. Restricted to the subcategory of central morphisms, ¬ is adjoint to itself on the right.

\frac{A ⟶ ¬B}{B ⟶ ¬A}

Intuitively, a central morphism A ⟶ ¬B expects an argument of type A and returns a B-accepting continuation. Hence there is demand for both A and B; and again the correspondence arises essentially by swapping.

The unit of this adjunction is a generic delaying map thunk : A ⟶ ¬¬A. Using thunk, we define a morphism

pair : C ⟶ ¬(A ⊗ ¬(A ⊗ C))

which is a natural transformation in the centre. This in turn is used to define λ-abstraction.

\overline{λ}_{A}f =^{def} pair; ¬(A ⊗ ¬f)

This definition of λ-abstraction in terms of control (and tuple types) does not give rise to a closed category, although we have the following.

(A ⊗ \overline{λ}f); apply = A ⊗ pair; A ⊗ ¬(A ⊗ ¬f); apply = A ⊗ pair; apply; f = f

The corresponding \overline{λ}(A ⊗ g; apply) = g, however does not hold in general. Hence this λ-abstraction does not give rise to a cartesian closed category. But it is still sufficient for interpreting a call-by-value λ-calculus, as a central g can be pushed into λ (and values denote central morphisms). Although neither SML nor Scheme make this identification of function types [A → B] with ¬(A ⊗ ¬B), we can still define a pair of coercion functions (figures 1.12 and 1.13).

The basis for our categorical account of continuation semantics will be the negation functor, corresponding to the typing based on ¬ in Chapter 2 However, the continuations considered there were actually polyadic, that is, in k⟨x_{1} … x_{i}⟩ k is applied to a tuple of arguments. That is why, before introducing ¬, we need some first-order structure for building up such tuples (as well as environments).

4.2 Semantics of environments in a premonoidal category

Before addressing the categorical semantics of environments, we should perhaps clarify what we mean by “environment” here. The terminology we adopt may not be completely standard, but is a rational one in being semantically motivated.

In Type Theory, the antecedent of a judgement x_{1} : τ_{1}, …, x_{n} : τ_{n} ⊢ M : τ is usually called a context. In Computer Science, an environment is traditionally a map from variables to values. Here we use the word environment in the general sense of anything that ascribes something (types, values, …) to variables; the former, then, is a type environment and the latter a value environment.

Semantically, a type environment denotes an object in some semantic category, while a value environment denotes an element thereof. (In the categorical sense of element: an element of an object being a morphism with that object as its codomain.)

This generalises the usual notion of types denoting objects and terms denoting their elements in a straightforward pointwise fashion to indexed collections of both: value and type environments, respectively.

In the sequel, we concentrate on the semantics of type environments and do not deal with value environments explicitly. But their semantics is implicit in that a morphism ⟦Γ⟧ ⟶ ⟦τ⟧ can be seen as mapping elements of ⟦Γ⟧ to elements of ⟦τ⟧.

In particular, suppose our semantic category consists of sets with structure, and that we build up the denotation of a type environment ⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ as the product of the denotations of the types ⟦τ_{i}⟧. Then a value environment ρ is (up to isomorphism) a (global) element of the denotation of the type environment

\xymatrix{1 \ar[r]^{ρ} & ⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ = ⟦τ_{1}⟧ × ⋯ × ⟦τ_{n}⟧}

This also implies a notion of value environments “matching” type environments in that ρ ascribes to each x_{i} a value v_{i} having type τ_{i}.

The above generalisation of environments is also closely related to the view of structures in Standard ML having the signatures which they match as their “type” [FT95], to the extent that an ML structure represents a value environment and a signature a type environment.

Another reason for avoiding the word “context” here is that in Computer Science this often refers to a notion of “term with a hole”, such as evaluation context. Using “context” in the sense of “type environment”, moreover, may lead to an unfortunate inversion of terminology if the word “environment” is then used for (the totality of) computing agents with which some interaction is possible, a concept more closely allied with “evaluation context” or indeed “continuation”.

We will use a premonoidal structure ⊗ for interpreting environments of the form ⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ as ⟦τ_{1}⟧ ⊗ ⋯ ⊗ ⟦τ_{n}⟧.

4.2.1 Definition ([PR97]) A strict premonoidal category is a category K together with an object 1 ∈ ObK and, for each A ∈ ObK, endofunctors A ⊗ ( ) and ( ) ⊗ A that agree in the sense that

(A ⊗ ( ))(B) = (( ) ⊗ B))(A) =: A ⊗ B

such that 1 ⊗ (−) = id_{K} = (−) ⊗ 1 and

(A ⊗ B) ⊗ C = A ⊗ (B ⊗ C)
(f ⊗ B) ⊗ C = f ⊗ (B ⊗ C)
(A ⊗ g) ⊗ C = A ⊗ (g ⊗ C)
(A ⊗ B) ⊗ h = A ⊗ (B ⊗ h)

A morphism f : A ⟶ A′ is called central if it commutes with everything in the sense that, for all g : B ⟶ B′, we have

A ⊗ g; f ⊗ B′ = f ⊗ B; A′ ⊗ g
g ⊗ A; B′ ⊗ f = B ⊗ f; g ⊗ A′

That is, for f to be central, we require these diagrams to commute

\xymatrix{A ⊗ B \ar[r]^{A⊗g} \ar[d]_{f⊗B} & A ⊗ B′ \ar[d]^{f⊗B′} & B ⊗ A \ar[r]^{g⊗A} \ar[d]_{B⊗f} & B′ ⊗ A \ar[d]^{B′⊗f} \\ A′ ⊗ B \ar[r]^{A′⊗g} & A′ ⊗ B′ & B ⊗ A′ \ar[r]^{g⊗A′} & B′ ⊗ A′}

The centre Z(K) of K is the subcategory of K consisting of all objects and all central morphisms. Let ι : Z(K) ↪ K be the inclusion.

The inclusion of the centre will often be left implicit.

4.2.2 Remark To simplify the account, we have concentrated on the strict case, rather than the more general premonoidal category. Because each premonoidal category is equivalent to a strict premonoidal category (implicit in [PR97]), the restriction to strictness is not a very severe one.

One could reasonably expect everything to generalise to the general case in a routine way.

In any case, the emphasis here is on the categorical structure of continuations, so we believe it to be defensible to postpone coherence issues until this is wellunderstood and definitive. — For the present, the canonical example is a term model (which we try to understand more abstractly), so it would be somewhat counterproductive not to take advantage of the strictness afforded by term models.

4.2.3 Definition A ⊗-category is a strict premonoidal category K such that ⊗ is given by cartesian product in the centre of K and furthermore, the twist map arising from this product

⟨π_{2}, π_{1}⟩ : A ⊗ B ⟶ B ⊗ A

is natural in A and and in B.

We extend the morphism pairing operation ⟨ , ⟩ given by the products in the centre of K to the whole of K as follows. (Note that this implies a choice of which component is computed first: here it is the second.) For f : C ⟶ A and g : C ⟶ B, let

⟨f, g⟩ =^{def} ⟨id_{C}, id_{C}⟩; C ⊗ g; f ⊗ B : C ⟶ A ⊗ B

4.2.4 Definition We say that a morphism f : A ⟶ B is copyable iff it respects the binary products of the centre in the sense that

f; ⟨id, id⟩ = ⟨f, f⟩ : A ⟶ B ⊗ B

and that f is discardable if it respects the terminal object 1 of the centre in the sense that

f; !_{B} =!_{A} : A ⟶ 1

In [Fil89], discardable morphisms are called total.

4.3 Continuation types as higher-order structure

We will be interested in a particularly simple kind of adjunction: a contravariant functor being adjoint to its own dual, with the unit and co-unit being the same.

4.3.1 Definition A functor F : C^{op} ⟶ C is called self-adjoint on the left iff there is a natural transformation ϵ : F F^{op} ⟶ id_{C} such that F ϵ; ϵF = id. Dually, F is called self-adjoint on the right iff F^{op} is self-adjoint on the left.

(See also [Mac71, p. 87] for the “on the left” idiom.)

The continuation functor ¬ has two universal properties, adjointness on the left and right; we axiomatise them here in terms of the universal maps apply and thunk, respectively.

4.3.2 Definition A ⊗¬-category is a ⊗-category K together with • a functor ¬ : K^{op} ⟶ Z(K) such that for each object A of K,

(A ⊗ ι¬( )) : K^{op} ⟶ K

is self-adjoint on the left (let apply_{A} : A ⊗ ¬(A ⊗ ¬B) ⟶ B be the unit of this adjunction), and

• a natural transformation thunk : id_{Z(K)} ⟶ ¬¬ in Z(K)

such that

• apply is dinatural in A

• thunk; force = id where force =^{def} apply_{1} : ¬¬A ⟶ A

• ¬ force = thunk ¬

• letting \underline{\underline{apply}}_{A} =^{def} A ⊗ ¬(A ⊗ force); apply_{A}, we have

¬ force = thunk ¬
thunk; ¬¬\underline{\underline{apply}} = \underline{\underline{apply}}; thunk
thunk_{A⊗C} = A ⊗ thunk_{C}; A ⊗ ¬\underline{\underline{apply}}; \underline{\underline{apply}}
apply_{A⊗A′} = ⟨π_{2}, π_{1}⟩ ⊗ ¬(A ⊗ A′ ⊗ ¬B); A′ ⊗ \underline{\underline{apply}}_{A}; apply_{A′}

The first of these four axioms establishes another link between forcing and thunking (in addition to the more familiar thunk; force = id); the second states that the call-by-name application, unlike the call-by-value one, is effect-free; the other two are somewhat technical coherence conditions.

Intuitively, dinaturality of the application map means that modifying the operand of a function application by a map f : A ⟶ A′ is the same as modifying the operator by a corresponding continuation transformer.

\xymatrix{A ⊗ ¬(A′ ⊗ ¬B) \ar[r]^{A⊗¬(f⊗¬B)} \ar[d]_{f⊗¬(A′⊗¬B)} & A ⊗ ¬(A ⊗ ¬B) \ar[d]^{apply} \\ A′ ⊗ ¬(A′ ⊗ ¬B) \ar[r]^{apply} & B}

Dinaturality is rquired as a separate axiom, as it does not seem to follow from naturality (unlike in a Cartesian closed category).

The universal property of the continuation functor can be expressed by the following diagrams (naturality and triangular identity for force.)

\xymatrix{¬¬A \ar[d]_{¬¬f} \ar[r]^{force} & A \ar[d]^{f} & ¬A \ar[r]^{¬force} \ar[dr]_{id} & ¬¬¬A \ar[d]^{force} \\ ¬¬B \ar[r]^{force} & B &  & ¬A}

In addition to the usual thunk; force = id, we have another axiom linking forcing and thunking. A consequence of this is the self-adjointness on the right of the restriction of ¬ to the centre, with unit thunk.

\xymatrix{A \ar[d]_{g} \ar[r]^{thunk} & ¬¬A \ar[d]^{¬¬g} & ¬A \ar[r]^{thunk} \ar[dr]_{id} & ¬¬¬A \ar[d]^{¬thunk} \\ B \ar[r]^{thunk} & ¬¬B &  & ¬A}

(where g is central.) In chapter 6 (figures 6.3 and 6.4), we will consider programs written in “compositional” style, that is, using sequential composition of functions and the programming analogues of thunk, ¬, … (figures 1.12 and 1.13). We hope that the simple and quite symmetric categorical laws expressed by the above diagrams could facilitate reasoning about programs written in this style.

4.3.3 Definition Given a cartesian closed category C (with strict products), and an object R of C we can define a ⊗¬-category K as follows

Ob K =^{def} Ob C
K(A, B) =^{def} C(R^{B}, R^{A})

A⊗( ) is given by the product A×( ) in C. The functor ¬ is R^{( )}. force =^{def} ηR^{( )} and thunk =^{def} R^{η}ηR^{( )}, where η_{A} : A ⟶ R^{R^{A}} is the unit of the “continuation monad” on C. We call K the standard model for C and R.

Despite the apparent generality of this construction, we regard this as an overly specific approach that does not do justice to the full generality of CPS (compare section 2.6). It consists essentially of implementing CPS in simplytyped λ-calculus and then interpreting this in the usual fashion in a cartesian closed category.

4.3.4 Remark In the category C, the functor [( ) → R] is self-adjoint on the right. The two isomorphic views of the continuation semantics category as a category of continuation transformers and as the Kleisli category of the continuation monad are connected by the self-adjointness (i.e. its isomorphism of adjunction).

C(A, R^{B}) ≅ C(B, R^{A})

This is also the connection between the typings of the Plotkin-style (continuation last) and Fischer-style (continuation first) CPS transforms. On types, this gives the continuation monad view \overline{σ → τ} = \overline{σ} → ¬¬τ, or the continuation transformer view \overline{σ → τ} = ¬\overline{τ} → ¬σ, respectively.

4.4 Some interdependencies of properties

We write ϕ_{A} for the isomorphism of adjunction for the self-adjointness on the left. This is an involution. ϕ_{A} : K(A ⊗ ¬B, C) ⟶ K(A ⊗ ¬C, B)

ϕ_{A}f =^{def} A ⊗ ¬f; apply_{A}

For A = 1, we have ϕ =^{def} ϕ_{1} : K(¬B, C) ⟶ K(¬C, B) with ϕf = ¬f; force.

4.4.1 Remark Note that because of the finite product structure on the centre, each functor A ⊗ ( ) comes equipped with a comonad structure, the unit being given by discarding, the multiplication by weakening.

We can regard ϕ_{A} as essentially the same as ϕ, but on the co-Kleisli category for the comonad A ⊗ ( )

Ob K_{A} =^{def} Ob K
K_{A}(B, C) =^{def} K(A ⊗ B, C)

For each A, K has its own “indexed negation” ¬_{A}, defined as

¬_{A}f =^{def} ϕ_{A}(A ⊗ force; f).

This “indexed functor” point of view has some advantages. In particular, some of the axioms of an ⊗¬-category become more comprehensible: they were essentially reverse-engineered to make ¬_{A} viable as an indexed functor. This issue will be addressed more fully in chapter 8 where we take the indexed category presentation as fundamental.

4.4.2 Remark What is perhaps surprising about this definition is that we have made such strong assumptions about the centre. All central morphisms are deemed to be effect-free, so that they respect the product. While centrality is certainly necessary for effect-freeness, there is in general no reason to assume that it is sufficient. It appears to be the presence of first-class continuations, specifically the unit force, that that makes centrality such a strong property: if a morphism commutes with everything, it must commute with force, and that implies that it commutes with reification. Slightly more technically, if f : A ⟶ B is central, then f ⊗ ¬¬¬B; B ⊗ force = A ⊗ force; f ⊗ ¬B. This implies the naturality of thunk, as

f; thunk
= A ⊗ thunk; A ⊗ ¬(f ⊗ ¬¬¬B; B ⊗ force; \underline{\underline{apply}}); apply
= A ⊗ thunk; A ⊗ ¬(A ⊗ force; f ⊗ ¬B; \underline{\underline{apply}}); apply
= thunk; ¬¬f

4.4.3 Remark If a morphism f is thunkable in the sense that it makes the naturality square

\xymatrix{A \ar[d]_{f} \ar[r]^{thunk} & ¬¬A \ar[d]^{¬¬f} \\ B \ar[r]^{thunk} & ¬¬B}

commute, then it respects binary products.

f; ⟨id, id⟩
= f; ⟨thunk; force, thunk; force⟩
= f; thunk; ⟨force, force⟩
= thunk; ¬¬f; ⟨force, force⟩
= ⟨thunk; ¬¬f; force, thunk; ¬¬f; force⟩
= ⟨f, f⟩

4.4.4 Remark Instead of defining ¬ to have the centre as it codomain, we could have required the adjoint correspondence

\frac{A ⊗ ¬B ⟶ C}{A ⊗ ¬C ⟶ B}

to be natural in A , as this implies that every morphism of the form ¬f is central. This property is perhaps more intuitive in terms of control flow: control manipulation concerning B and C does not affect a separate strand of control g : A ⟶ A′.

4.4.5 Proposition Every negated morphism \xymatrix{¬C \ar[r]^{¬f} & ¬B} is central.

Proof Let \xymatrix{B \ar[r]^{f} & C} and \xymatrix{A \ar[r]^{g} & A′} . We need to show that

\xymatrix{A ⊗ ¬C \ar[r]^{A⊗¬f} & A ⊗ ¬B \ar[r]^{g⊗¬B} & A′ ⊗ ¬B}

and

\xymatrix{A ⊗ ¬C \ar[r]^{g⊗¬C} & A′ ⊗ ¬C \ar[r]^{A′⊗¬f} & A′ ⊗ ¬B}

are the same morphism. Applying ϕ_{A} to the first composite, we get

ϕ_{A}(A ⊗ ¬f; g ⊗ ¬B)
= ϕ_{A}(A ⊗ ¬f; g ⊗ ¬B; id_{A′⊗¬B})
= ϕ_{A}(g ⊗ ¬B; id_{A′⊗¬B}); f
= g ⊗ ¬(A′ ⊗ ¬B); ϕ_{A′}(id_{A′⊗¬B}); f

For the second composite:

ϕ_{A}(g ⊗ ¬C; A′ ⊗ ¬f)
= ϕ_{A}(g ⊗ ¬C; A′ ⊗ ¬f; id_{A′⊗¬B})
= g ⊗ ¬(A′ ⊗ ¬B); ϕ_{A′}(A′ ⊗ ¬f; id_{A′⊗¬B})
= g ⊗ ¬(A′ ⊗ ¬B); ϕ_{A′}(id_{A′⊗¬B}); f

Because ϕ_{A} is an isomorphism, this means that A⊗¬f; g⊗¬B = g⊗¬C; A′⊗¬f.

□

4.4.6 Proposition

\xymatrix{A′ ⊗ A ⊗ ¬(A ⊗ A′ ⊗ ¬B) \ar[r]^{A′⊗A⊗¬(A⊗force)} \ar[dd]^{≅} & A′ ⊗ A ⊗ ¬(A ⊗ ¬¬(A′ ⊗ ¬B)) \ar[d]^{A′⊗apply_{A}} \\  & A′ ⊗ ¬(A′ ⊗ ¬B) \ar[d]^{_{apply_{A′}}} \\ A ⊗ A′ ⊗ ¬(A ⊗ A′ ⊗ ¬B) \ar[r]^{_{apply_{A⊗A′}}} & B}

could equivalently be expressed in terms of coherence for the indexed negation

\xymatrix{A′ ⊗ A ⊗ ¬(A ⊗ A′ ⊗ B) \ar[d]^{≅} \ar[r]^{A′⊗¬_{A}id_{A⊗A′⊗B}} & A′ ⊗ ¬(A′ ⊗ B) \ar[d]^{¬_{A′}id_{A′⊗B}} \\ A ⊗ A′ ⊗ ¬(A ⊗ A′ ⊗ B) \ar[r]^{¬_{A⊗A′}id_{A⊗A′⊗B}} & ¬B}

4.5 λ-abstraction in a ⊗¬-category

Just as in the standard CPS transforms, function types σ → τ will be decomposed into continuations for arguments σ and result continuations ¬τ. So instead of exponentials, we have a derived notion of arrow type

[A → B] =^{def} ¬(A ⊗ ¬B)

The corresponding application map is the unit of the adjunction

apply_{A} : A ⊗ ¬(A ⊗ ¬B) ⟶ B

In a cartesian closed category, we could define λ-abstraction in terms of the right adjoint [A → ( )] in A × ( ) ⊣ [A → ( )] and the unit of adjunction (the curried pairing map) pair : C ⟶ [A ⟶ (A × C)] as λf =^{def} pair; [A → f]. The notion of λ-abstraction that we have in the present setting can be defined in a way that is formally very similar, although we do not have cartesian closure.

We define a pairing map as

pair =^{def} thunk_{C}; ¬(A ⊗ ¬(A ⊗ force); apply_{A}) : C ⟶ ¬(A ⊗ ¬(A ⊗ C))

Note that of the two possible composites of the functors A ⊗ ( ) and ¬, one is self-adjoint on the left, the other on the right. A ⊗ ¬( ) is self-adjoint on the left ¬(A ⊗ ( )) restricted to the centre is self-adjoint on the right.

The pairing map then allows us to define (call-by-value) λ-abstraction.

\overline{λ}f =^{def} pair; ¬(A ⊗ ¬f)

Although we may read apply and pair as having the types familiar from cartesian closed categories, that is

apply : A ⊗ [A → B] ⟶ B
pair : C ⟶ [A → (A ⊗ C)]

this is really a kind of secondary etymology, as in reality apply and pair are the unit/counit of negation functors A ⊗ ¬( ) and ¬(A ⊗ ( )), respectively.

apply : A ⊗ ¬(A ⊗ ¬B) ⟶ B
pair : C ⟶ ¬(A ⊗ ¬(A ⊗ C))

4.5.1 Proposition The following two diagrams commute

\xymatrix{A ⊗ C \ar[r]^{A⊗pair} \ar[dr]_{_{id_{A⊗C}}} & A ⊗ ¬(A ⊗ ¬(A ⊗ C)) \ar[d]^{apply} \\  & A ⊗ C}

\xymatrix{¬(A ⊗ ¬B) \ar[r]^{pair} \ar[dr]_{_{id_{¬(A⊗¬B)}}} & ¬(A ⊗ ¬(A ⊗ ¬(A ⊗ ¬B))) \ar[d]^{¬(A⊗¬apply)} \\  & ¬(A ⊗ ¬B)}

In the framework of premonoidal categories, a notion of call-by-value λ-abstraction was proposed [Pow]. Formally, this is a mild variation on monoidal closure, requiring not A ⊗ ( ), but it composition with the inclusion of the centre to have a right adjoint, hence the name “central” closure.

4.5.2 Proposition A ⊗¬-category is centrally closed, in the sense that

ι(( ) ⊗ A) ⊣ ¬(A ⊗ ¬( ))

where ι : Z(K) ↪ K. In terms of equations:

A ⊗ \overline{λ}(f); apply = f
\overline{λ}(A ⊗ g; apply) = g
\overline{λ}(A ⊗ g; f) = g; \overline{λ}f
\overline{λ}f; ¬(A ⊗ ¬h) = \overline{λ}(f; h)

where g is central.

The fact that pair is not natural with respect to all morphisms is what makes the “protecting by a λ” technique work in this setting. (It is really the precomposition with thunk that does the protecting.)

4.5.3 Corollary The inclusion of the centre is left adjoint to double negation.

ι ⊣ ¬¬

The isomorphism of adjunction is

λ_{1} : K(A, B) ⟶ Z(K)(A, ¬¬B)
f ↦ thunk; ¬¬f

This is a categorical formulation of thunking as a form of reification, given here by f ↦ thunk; ¬¬f.

4.5.4 Corollary K is the Kleisli category for the monad ¬¬ on Z(K).

We nonetheless prefer to regard the category of computations K as primary, rather than reifying everything and then running a Kleisli interpreter on top of it.

4.5.5 Proposition The call-by-name application and abstraction satisfy the following:

A ⊗ \underline{\underline{λ}}_{A}f; \underline{\underline{apply}}_{A} = f
\underline{\underline{λ}}_{A}(A ⊗ g; \underline{\underline{apply}}_{A}) = g

Moreover, \underline{\underline{apply}}_{A} is itself central.

It could appear as if we were somehow recovering a Cartesian closed category. But that is not really the case. Although the centre does have finite products, the centrally closed structure given by apply does not restrict to the centre, as apply is not itself central. Intuitively, this is because the application map is a jump with arguments, and jumps are too effectful to be central. So there is a trade-off of sorts: one can either have the products or the higher-order structure.

The CPS term model

In this chapter, we build a term model of a ⊗¬-category from the syntax of (simply-typed) CPS calculus. This is analogous to the (standard) construction of a Cartesian closed category from simply-typed λ-calculus (e.g. [Cro93]).

The CPS calculus is of course rather different in style from λ-calculi, so instead of familiar structures like (Cartesian) closure, or its generalisation to other binding constructs as adjoints to reindexing ^{1}, we get self-adjointness as the algebraic manifestation of first-class jumping (hinted at in section 1.2.1 in the introduction).

5.1 Building a category from CPS terms

In this section we will attempt to isolate the crucial structure that makes continuation semantics work, gradually abstracting from the CPS calculus to arrive at a syntax free-presentation that will lead to a categorical semantics in the next section.

The jumping and the binding construct of CPS calculus correspond to identities and composition in the term model.

The self-adjointness on the left is responsible for various generalised jumps (which eliminates double-negations); while the self-adjointness on the right builds new places to jump to (which introduces a double negation).

5.1.1 First-order structure

In [App92], CPS terms are likened to the machine code of a von Neumann machine (as far as control is concerned). But as far as sequential composition is concerned, CPS terms are even more low-level than code, as they do not have a default or current continuation. The basic idea, then, is to ascribe meaning not to a CPS term by itself, but to a CPS typing judgement that lists the free variables of the term, distinguishing one of them as the current continuation.

1Consider for instance the definitions of ∏, ∑, ∀, ∃ in a topos

The type environment part x⃗:σ⃗, k:¬τ⃗ of a judgement x⃗:σ⃗, k:¬τ⃗ ⊢ M gives each morphism [x⃗:σ⃗, k:¬τ⃗ ⊢ M] a unique domain and codomain, as required. Conversely, if the domain σ⃗ and codomain τ⃗ are clear from the context, we can write more succinctly [x⃗k ⊢ M].

Once a current continuation has been singled out in a judgement, there is a natural concept of sequential composition:

[x⃗n ⊢ M]; [y⃗k ⊢ N] =^{def} [x⃗k ⊢ M{n⟨y⃗⟩=N}]

This has [x⃗k ⊢ k⟨x⃗⟩] as its identity.

Furthermore, we have product types (by concatenation); and although these are not categorical products, we do have projections π_{i} = [x⃗_{1}x⃗_{2}k ⊢ k⟨x⃗_{i}⟩] as well as contraction δ = [x⃗k ⊢ k⟨x⃗x⃗⟩].

Hence we have enough parallelism on types to accommodate multi-arity maps denoting program phrases of more than one free variable. Given the sequential nature of CPS, we would not expect genuine parallelism of morphisms (denoting terms) f ⊗ g.

Given two CPS judgements

[x⃗k ⊢ M] [y⃗h ⊢ N]

there are in general two different composites; we can run either M or N first. In each case, the term first to be evaluated has to carry along the free variables needed by the second judgement. If we run M first, this gives a judgement [x⃗y⃗k′ ⊢ M{k⟨z⃗⟩=k′⟨x⃗z⃗⟩}]

But implicit in this composition of M and N was the notion of “carrying along free variables”. Considered on its own this is, for every object A, a functor A⊗( ) defined by

A ⊗ [x⃗k ⊢ M] =^{def} [y⃗x⃗k′ ⊢ M{k⟨z⃗⟩=k⟨y⃗z⃗⟩}]

Symmetrically, we have a functor ( ) ⊗ A.

5.1.2 Application as double negation elimination

All the above are trivial terms in the sense that they jump to the current continuation. A CPS judgement that does not do this is evidently [hk ⊢ h⟨k⟩]. But this is just the identity [kh ⊢ h⟨k⟩] with the argument and the current continuation interchanged.

More generally, given any [hk ⊢ M] with a continuation parameter h, let

ϕ[hk ⊢ M] =^{def} [kh ⊢ M]

¬ extends to a functor. We can define it in terms of ϕ and force as ¬f = ϕ(force; f). Concretely, this boils down to the following:

¬[x⃗h ⊢ M]
= ϕ(force; [x⃗h ⊢ M])
= ϕ([kl ⊢ k⟨l⟩]; [x⃗h ⊢ M])
= ϕ[kh ⊢ k⟨l⟩{l⟨x⃗⟩=M}]
= [hk ⊢ k⟨l⟩{l⟨x⃗⟩=M}]

The switching operation does not interfere with any other names x⃗ in the environment, that is, a judgement [x⃗hk ⊢ M] can be switched to yield [x⃗kh ⊢ M]. And this is natural.

This switching operation is quite unfamiliar from direct-style programming, but used on identities, projections and contraction it gives rise to some of the most important idioms that we need for the interpretation of λ-calculus with control.

force switching argument h and current continuation k in the identity [hk ⊢ k⟨h⟩]

gives

force : [kh ⊢ k⟨h⟩] : ¬¬A ⟶ A

apply switching argument f and current continuation k in the identity gives

apply : [x⃗fk ⊢ f⟨x⃗k⟩] : A ⊗ ¬(A ⊗ ¬B) ⟶ B

throw switching h argument and current continuation k in the projection π_{1} = [x⃗hk ⊢ k⟨x⃗⟩] gives

throw = [x⃗hk ⊢ k⟨x⃗⟩] : A ⊗ ¬A ⟶ B

callcc switching argument k and current continuation f in [kf ⊢ f⟨kk⟩] gives

[fk ⊢ f⟨kk⟩] : ¬(¬A ⊗ ¬A) ⟶ A

Note that the clauses for application, throw and callcc in figure 3.1.1 consist essentially of composing with one of these constants, while force is essentially the denotation of a free variable in call-by-name.

5.1.3 Thunking as double negation introduction

The units of the self-adjointness on the right wrap their argument into a thunk.

thunk = [x⃗k ⊢ h⟨f⟩{f⟨h⟩=h⟨x⃗⟩}]

pair = [x⃗k ⊢ h⟨f⟩{f⟨y⃗h⟩=h⟨y⃗x⃗⟩}]

5.2 The ⊗¬ term model

5.2.1 Definition The category K(CPS) is constructed as follows. Objects are sequences τ⃗ of types. A morphism from σ⃗ to τ⃗ is an equivalence class [x⃗:σ⃗, k:¬τ⃗ ⊢ M] of judgements, where x⃗:σ⃗, k:¬τ⃗ ⊢ M and x′:σ′, k′:¬τ′ ⊢ M′ are equivalent iff

M = M′[x′k′ ↦ x⃗k]

is derivable.

ObK(CPS) = {τ_{1} … τ_{j} | τ_{i} is a CPS type expression}
K(CPS)(σ⃗, τ⃗) = {[x⃗ : σ⃗, k : ¬τ⃗ ⊢ M] | x⃗ : σ⃗, k : ¬τ⃗ ⊢ M is derivable}

Identities and composition correspond to the two term-forming rules of CPS calculus.

\begin{aligned}idσ⃗ & = & [x⃗:σ⃗, k:¬σ⃗ ⊢ k⟨x⃗⟩] \\ [x⃗:τ⃗_{1}, n:¬τ⃗_{2} ⊢ M]; [y⃗:τ⃗_{2}, h:¬τ⃗_{3} ⊢ M] & = & [x⃗:τ⃗_{1}, h:¬τ⃗_{3} ⊢ M\{n⟨y⃗⟩=N\}] \end{aligned}

The structure on morphisms is given as follows:

¬[x⃗:σ⃗, h:¬τ⃗ ⊢ M] = [h:¬τ⃗, k:¬¬σ⃗ ⊢ k⟨f⟩{f⟨x⃗⟩=M}]
[x⃗:σ⃗_{1}, k:¬τ⃗ ⊢ M] ⊗ σ⃗_{2} = [x⃗:σ⃗_{1}, y⃗:σ⃗_{2}, h:¬(τ⃗σ⃗_{2}) ⊢ M{k⟨z⃗⟩=h⟨z⃗y⃗⟩}]
σ⃗_{1} ⊗ [x⃗:σ⃗_{2}, k:¬τ⃗ ⊢ M] = [y⃗:σ⃗_{1}, x⃗:σ⃗_{2}, h:¬(σ⃗_{1}τ⃗) ⊢ M{k⟨z⃗⟩=h⟨y⃗z⃗⟩}]
applyσ⃗ = [x⃗:σ⃗, f:¬(σ⃗, ¬τ⃗), k:¬τ⃗ ⊢ f⟨x⃗k⟩]
thunkτ⃗ = [x⃗:τ⃗, k:¬¬¬τ⃗ ⊢ k⟨f⟩{f⟨h⟩=h⟨x⃗⟩}]

5.2.2 Remark We should point out that focussing on the term model is not really a restriction to syntactic, as opposed to semantic models.

Consider the definition of the premonoidal structure on the category of continuation transformers on a Cartesian closed category, where a morphism is of the following form:

Φ : R^{A} ⟵ R^{B}

It would be easy to say that C ⊗ Φ is given by virtue of the functor ( )^{C} and the evident isomorphims

R^{C×A} ≅ (R^{A})^{C} ⟵ (R^{B})^{C} ≅ R^{C×B}

As soon as definitions become more complicated, that style becomes hopeless and one needs to adopt a more systematic approach by using the internal langauge of the Cartesian closed category, that is simply-typed λ-calculus with products and a constant Φ for every morphism Φ in the category. Then we can write the definition more concisely and rigorously as:

C ⊗ Φ =^{def} ⟦k : R^{C×A} ⊢ λ(c, a) : C × A.Φ (λb.k (c, b)) a : R⟧

Up to ordering of variables, this is essentially the definition in the CPS term model, with CPS specialised to the λ-calculus. Moreover, the axioms of the CPS calculus are sound for the translation to λ-calculus, hence proofs are translated to proofs about (the internal language of) Cartesian closed categories.

Reasoning within the CPS term model is thus similar to using the internal language of a Cartesian closed category or the internal logic of a topos instaed of doing diagram chases.

5.2.3 Proposition K(CPS) as defined in Definition 5.2.1 is in fact a category.

Proof

• id is the identity

id; [y⃗h ⊢ N]
= [x⃗n ⊢ n⟨x⃗⟩]; [y⃗h ⊢ N]
= [x⃗h ⊢ n⟨x⃗⟩{n⟨y⃗⟩=N}]
= [x⃗h ⊢ N[y⃗ ↦ x⃗]]
= [y⃗h ⊢ N]
[x⃗n ⊢ M]; id
= [x⃗n ⊢ M]; [y⃗h ⊢ h⟨y⃗⟩]
= [x⃗h ⊢ M{n⟨y⃗⟩=h⟨y⃗⟩}]
= [x⃗h ⊢ M[n ↦ h]]
= [x⃗n ⊢ M]

• composition is associative

([x⃗m ⊢ L]; [y⃗n ⊢ M]) ; [z⃗k ⊢ N]

= [x⃗n ⊢ L{m⟨y⃗⟩=M}]; [z⃗k ⊢ N]
= [x⃗k ⊢ L{m⟨y⃗⟩=M}{n⟨z⃗⟩=N}]
= [x⃗k ⊢ L{n⟨z⃗⟩=N}{m⟨y⃗⟩=M{n⟨z⃗⟩=M}}]
= [x⃗k ⊢ L{m⟨y⃗⟩=M{n⟨z⃗⟩=M}}]
= [x⃗m ⊢ L]; ([y⃗n ⊢ M]; [z⃗k ⊢ N])
□

5.2.4 Lemma A morphism [x⃗k ⊢ M] is central iff for all N with z⃗ ∉ FV(N), w⃗ ∉ FV(M)

M{k⟨z⃗⟩=N{h⟨w⃗⟩=l⟨z⃗w⃗⟩}} = N{h⟨w⃗⟩=M{k⟨z⃗⟩=l⟨z⃗w⃗⟩}}

Proof Let f = [x⃗k ⊢ M] : A ⟶ A′ be central. Then for all g = [y⃗h ⊢ N] : B → B′, f ⊗ B; A′ ⊗ g = A ⊗ g; f ⊗ B′.

f ⊗ B; A′ ⊗ g
= [x⃗y⃗l ⊢ M{k⟨z⃗⟩=n⟨z⃗y⃗⟩{n⟨z⃗y⃗⟩=N{h⟨w⃗⟩=l⟨z⃗w⃗⟩}}}]
= [x⃗y⃗l ⊢ M{k⟨z⃗⟩=N{h⟨w⃗⟩=l⟨z⃗w⃗⟩}}]
A ⊗ g; f ⊗ B′
= [x⃗y⃗l ⊢ N{h⟨w⃗⟩=m⟨x⃗w⃗⟩{m⟨x⃗w⃗⟩=M{k⟨x⃗⟩=z⃗w⃗⟨}⟩}}]
= [x⃗y⃗l ⊢ N{h⟨w⃗⟩=M{k⟨z⃗⟩=l⟨z⃗w⃗⟩}}]
□

For instance, the identity [x⃗k ⊢ k⟨x⃗⟩] is central, but force = [hk ⊢ h⟨k⟩] is not: take M = x⟨k⟩ and N = y⟨h⟩.

5.2.5 Lemma If a morphism f is central, then thunk; ¬¬f = f; thunk.

Proof If f = [x⃗k ⊢ M] is central, then by Lemma 5.2.4,

M{k⟨y⃗⟩=h⟨f⟩{f⟨l⟩=z⟨y⃗l⟩}} = h⟨f⟩{f⟨l⟩=M{k⟨y⃗⟩=z⟨y⃗l⟩}}

Hence, applying {z⟨y⃗l⟩=l⟨y⃗⟩}, we have

M{k⟨y⃗⟩=h⟨f⟩{f⟨l⟩=z⟨y⃗l⟩}}{z⟨y⃗l⟩=l⟨y⃗⟩}
= h⟨f⟩{f⟨l⟩=M{k⟨y⃗⟩=z⟨y⃗l⟩}}{z⟨y⃗l⟩=l⟨y⃗⟩}

And this simplifies to

M{k⟨y⃗⟩=h⟨f⟩{f⟨l⟩=l⟨y⃗⟩}} = h⟨f⟩{f⟨k⟩=M}  (5.1)

Now

¬¬f
= ¬¬[x⃗k ⊢ M]
= ¬[kp ⊢ p⟨g⟩{g⟨x⃗⟩=M}]
= [ph ⊢ h⟨f⟩{f⟨k⟩=p⟨g⟩{g⟨x⃗⟩=M}}]
thunk; ¬¬f
= [x⃗n ⊢ n⟨p⟩{p⟨k⟩=k⟨x⃗⟩}]; [ph ⊢ h⟨f⟩{f⟨k⟩=p⟨g⟩{g⟨x⃗⟩=M}}]
= [x⃗h ⊢ n⟨p⟩{p⟨k⟩=k⟨x⃗⟩}{n⟨p⟩=h⟨f⟩{f⟨k⟩=p⟨g⟩{g⟨x⃗⟩=M}}}]

= [x⃗h ⊢ h⟨f⟩{f⟨k⟩=p⟨g⟩{g⟨x⃗⟩=M}}{p⟨k⟩=k⟨x⃗⟩}]
= [x⃗h ⊢ h⟨f⟩{f⟨k⟩=g⟨x⃗⟩{g⟨x⃗⟩=M}}]
= [x⃗h ⊢ h⟨f⟩{f⟨k⟩=M}]
f; thunk
= [x⃗k ⊢ M]; [y⃗h ⊢ h⟨f⟩{f⟨l⟩=l⟨y⃗⟩}]
= [x⃗h ⊢ M{k⟨y⃗⟩=h⟨f⟩{f⟨l⟩=l⟨y⃗⟩}}]

So by (5.1), thunk; ¬¬f = f; thunk.

□

This implies that thunk is a natural transformation in the centre.

5.2.6 Remark The isomorphism of adjunction of the self-adjointness on the right is a map ψ : hom(A, ¬B) ⟶ hom(B, ¬A) defined by

ψ[x⃗k ⊢ M] = [y⃗h ⊢ k⟨f⟩{f⟨x⃗⟩=M{k⟨l⟩=l⟨y⃗⟩}}]

This is an involution when restricted to central morphisms; for general morphisms, it is not quite an involution:

ψψ[x⃗k ⊢ M] = [x⃗k ⊢ k⟨g⟩{g⟨y⃗⟩=M{k⟨l⟩=l⟨y⃗⟩}}]

Consider x⃗ = a and M = a⟨k⟩.

5.2.7 Proposition The centre of K(CPS) has finite products: ⊗ together with the evident projections

π_{j} =^{def} [x⃗_{1}x⃗_{2}k ⊢ k⟨x⃗_{j}⟩]

is a product in the centre of K(CPS); and the empty sequence together with evident morphism [x⃗k ⊢ k⟨⟩] is a terminal object in the centre.

Proof For f_{i} = [x⃗k_{i} ⊢ M_{i}], let

⟨f_{1}, f_{2}⟩ =^{def} [x⃗h ⊢ M_{1}{k_{1}⟨y⃗_{1}⟩=M_{2}{k_{2}⟨y⃗_{2}⟩=h⟨y⃗_{1}y⃗_{2}⟩}}].

⟨π_{1}, π_{2}⟩
= ⟨[x⃗_{1}k_{1} ⊢ k_{1}⟨x⃗_{1}⟩], [x⃗_{2}k_{2} ⊢ k_{2}⟨x⃗_{2}⟩]⟩
= [x⃗_{1}x⃗_{2}h ⊢ k_{1}⟨x⃗_{1}⟩{k_{1}⟨x⃗_{1}⟩=k_{2}⟨x⃗_{2}⟩{k_{2}⟨x⃗_{2}⟩=h⟨x⃗_{1}x⃗_{2}⟩}}]
= [x⃗_{1}x⃗_{2}h ⊢ h⟨x⃗_{1}x⃗_{2}⟩]
= id

Note that central morphisms can be copied and discarded because the corresponding thunks can. This is the key to the proof, the remainder being routine manipulations of the definition of ⟨ , |_{⟩}. Let [x⃗k ⊢ M] be central. Then

M{k⟨y⃗⟩=h⟨f⟩{f⟨l⟩=l⟨y⃗⟩}} = h⟨f⟩{f⟨k⟩=M}

Therefore

M{k⟨y⃗⟩=l⟨⟩}
= M{k⟨y⃗⟩=h⟨f⟩{f⟨k⟩=k⟨y⃗⟩}}{h⟨f⟩=l⟨⟩}
= h⟨f⟩{f⟨k⟩=M}{h⟨f⟩=l⟨⟩}
= l⟨⟩{f⟨k⟩=M}
= l⟨⟩

Similarly, we have

M{k⟨y⃗⟩=l⟨y⃗y⃗⟩}
= M{k⟨y⃗⟩=p⟨y⃗⟩{p⟨y⃗_{1}⟩=q⟨y⃗⟩{q⟨y⃗_{2}⟩=l⟨y⃗_{1}y⃗_{2}⟩}}}
= M{k⟨y⃗⟩=f⟨p⟩{p⟨y⃗_{1}⟩=f⟨q⟩{q⟨y⃗_{2}⟩=l⟨y⃗_{1}y⃗_{2}⟩}}{f⟨r⟩=r⟨y⃗⟩}}
= M{k⟨y⃗⟩=h⟨f⟩{f⟨r⟩=r⟨y⃗⟩}}{h⟨f⟩=f⟨p⟩{p⟨y⃗_{1}⟩=f⟨q⟩{q⟨y⃗_{2}⟩=l⟨y⃗_{1}y⃗_{2}⟩}}}
= h⟨f⟩{f⟨k⟩=M}{h⟨f⟩=f⟨p⟩{p⟨y⃗_{1}⟩=f⟨q⟩{q⟨y⃗_{2}⟩=l⟨y⃗_{1}y⃗_{2}⟩}}}
= f⟨p⟩{p⟨y⃗_{1}⟩=f⟨q⟩{q⟨y⃗_{2}⟩=l⟨y⃗_{1}y⃗_{2}⟩}}{f⟨k⟩=M}
= M{k⟨y⃗_{1}⟩=M{k⟨y⃗_{2}⟩=l⟨y⃗_{1}y⃗_{2}⟩}}

This means that, for a central morphism g, we have g; ⟨id, id⟩ = ⟨g, g⟩. Let g : A ⟶ B and f_{i} : B ⟶ C_{i}. Then

g; ⟨f_{1}, f_{2}⟩
= g; ⟨id, id⟩; B ⊗ f_{2}; C_{1} ⊗ f_{2}
= ⟨id, id⟩; A ⊗ g; g ⊗ B; B ⊗ f_{2}; f_{1} ⊗ C_{2}
= ⟨id, id⟩; A ⊗ g; A ⊗ f_{2}; g ⊗ C_{2}; f_{1} ⊗ C_{2}
= ⟨id, id⟩; A ⊗ (g; f_{2}); (g; f_{1}) ⊗ C_{2}
= ⟨g; f_{1}, g; f_{2}⟩

Therefore, for central g_{i}, ⟨g_{1}, g_{2}⟩; π_{j} = g_{j} □

5.2.8 Lemma A morphism f : A ⟶ ¬A′ is central iff f is of the form

[x⃗k ⊢ k⟨f⟩{f⟨y⃗⟩=M′}]

with k ∉ FV(M′).

Proof Let N = b⟨h⟩ with b and h fresh. Because [x⃗k ⊢ M] is central,

M{k⟨a⟩=b⟨h⟩{h⟨y⃗⟩=l⟨ay⃗⟩}} = b⟨h⟩{h⟨y⃗⟩=M{k⟨a⟩=l⟨ay⃗⟩}}

Applying ( ){l⟨ay⃗⟩=a⟨y⃗⟩} to both sides, we get

M{k⟨a⟩=b⟨h⟩{h⟨y⃗⟩=l⟨ay⃗⟩}}{l⟨ay⃗⟩=a⟨y⃗⟩}
= b⟨h⟩{h⟨y⃗⟩=M{k⟨a⟩=l⟨ay⃗⟩}}{l⟨ay⃗⟩=a⟨y⃗⟩}

Simplifying this yields

M[k ↦ b] = b⟨h⟩{h⟨y⃗⟩=M{k⟨a⟩=a⟨y⃗⟩}}

Hence

f
= [x⃗k ⊢ M]
= [x⃗b ⊢ M[k ↦ b]]
= [x⃗b ⊢ b⟨h⟩{h⟨y⃗⟩=M{k⟨a⟩=a⟨y⃗⟩}}]
= [x⃗b ⊢ b⟨h⟩{h⟨y⃗⟩=M′}]

for M′ = M{k⟨a⟩=a⟨y⃗⟩} and b ∉ FV(M′), as b is fresh. □

5.2.9 Definition (Trivial CPS term) A CPS term is called trivial in k iff it is of the form

k⟨p_{1} … p_{n}⟩{p_{i_{1}}⟨x⃗_{i_{1}}⟩=M_{i_{1}}} … {p_{i_{m}}⟨x⃗_{i_{m}}⟩=M_{i_{m}}}

with k ∉ FV(M_{i_{j}}). ({i_{1}, …, i_{m}} ⊆ {1, …, n})

5.2.10 Proposition Suppose that there are no base types in CPS calculus. Then a morphism f : A ⟶ B in K(CPS) is central iff f = [x⃗k ⊢ M] such that M is trivial in k.

This means we can find a trivial representative for the equivalence class, not that all representatives are of this form (one could simply expand a redex). For instance,

id = [x⃗k ⊢ k⟨x⃗⟩] = [x⃗k ⊢ n⟨h⟩{h⟨k⟩=k⟨x⃗⟩}{n⟨h⟩=h⟨k⟩}] = thunk; force

Proof By cases on B. If B = ¬τ, apply lemma 5.2.8. Otherwise, B = τ_{0}τ_{1} … τ_{n}, and we proceed by induction on n. Since B is a sequence of type expressions, we split off the first one, which must be of the form τ_{0} = ¬σ. Apply the induction hypothesis to f; π_{2} : A ⟶ τ_{1} … τ_{n}. By lemma 5.2.8, f; π_{1} : A ⟶ τ_{0} = ¬σ, defined as

f; π_{1} = [w⃗h ⊢ M{k⟨py⃗⟩=h⟨p⟩}]

must be of the form

f; π_{1} = [w⃗h ⊢ h⟨p⟩{p⟨x⃗⟩=M′}]

As f is central, f = ⟨f; π_{1}, f; π_{2}⟩. Then

⟨f; π_{1}, f; π_{2}⟩
= [w⃗k ⊢ M{k⟨py⃗⟩=h⟨p⟩{h⟨p⟩=M{k⟨qz⃗⟩=k⟨pz⃗⟩}}}]
= [w⃗k ⊢ M{k⟨py⃗⟩=h⟨p⟩}{h⟨p⟩=M{k⟨qz⃗⟩=k⟨pz⃗⟩}}]
= [w⃗k ⊢ h⟨p⟩{p⟨x⃗⟩=M′}{h⟨p⟩=M{k⟨qz⃗⟩=k⟨pz⃗⟩}}]
= [w⃗k ⊢ M{k⟨qz⃗⟩=k⟨pz⃗⟩}{p⟨x⃗⟩=M′}]
= [w⃗k ⊢ k⟨pp_{1} … p_{n}⟩{p_{i_{1}}⟨x⃗_{i_{1}}⟩=M_{i_{1}}} … {p_{i_{m}}⟨x⃗_{i_{m}}⟩=M_{i_{m}}}{p⟨x⃗⟩=M′}]
□

5.2.11 Conjecture We conjecture that respecting the finite product structure can be characterised syntactically by the occurrence of the current continuation.

• A morphism in K(CPS) is cancellable iff it is of the form

[x⃗k ⊢ k⟨p_{1} … p_{l}⟩{n_{1}⟨y⃗_{1}⟩=N_{1}} … {n_{m}⟨y⃗_{m}⟩=N_{m}}]

• A morphism in K(CPS) is copyable iff it is of the form

[x⃗k ⊢ q⟨p_{1} … p_{l}⟩{n_{1}⟨y⃗_{1}⟩=N_{1}} … {n_{m}⟨y⃗_{m}⟩=N_{m}}]

with k ∉ FV(N_{j}).

5.2.12 Proposition K(CPS) is a ⊗¬-category.

Proof

• ¬ is functorial: ¬ preserves identities

¬idτ⃗
= ¬[x⃗h ⊢ h⟨x⃗⟩]
= [hk ⊢ k⟨f⟩{f⟨x⃗⟩=h⟨x⃗⟩}]
= [hk ⊢ k⟨h⟩]
= id_{¬τ⃗}

¬ preserves composition

¬[y⃗h ⊢ N]; ¬[x⃗n ⊢ M]

= [hk′ ⊢ k′⟨n⟩{n⟨y⃗⟩=N}]; [nk ⊢ k⟨m⟩{m⟨x⃗⟩=M}]
= [hk ⊢ k′⟨n⟩{n⟨y⃗⟩=N}{k′⟨n⟩=k⟨m⟩{m⟨x⃗⟩=M}}]
= [hk ⊢ k⟨m⟩{m⟨x⃗⟩=M{n⟨y⃗⟩=N}}]
= ¬[x⃗h ⊢ M{n⟨y⃗⟩=N}]
= ¬ ([x⃗n ⊢ M]; [y⃗h ⊢ N])

• ϕ_{A} : K(A ⊗ ¬B, C) ≅ K(A ⊗ ¬C, B) is natural in B and C, i.e. ϕ_{A}(A ⊗

¬g; f) = ϕ_{A}(f; g)

(σ⃗ ⊗ ¬[y⃗h′ ⊢ N]) ; ϕσ⃗[x⃗kn ⊢ M]

= σ⃗ ⊗ [hk′ ⊢ k′⟨n⟩{n⟨y⃗⟩=N}]; [x⃗nk ⊢ M]
= [x⃗hh′ ⊢ k′⟨n⟩{n⟨y⃗⟩=N}{k′⟨n⟩=h′⟨x⃗n⟩}]; [x⃗nh ⊢ M]
= [x⃗hk ⊢ k′⟨n⟩{n⟨y⃗⟩=N}{k′⟨n⟩=h′⟨x⃗n⟩}{h′⟨x⃗n⟩=M}]
= [x⃗hk ⊢ h′⟨x⃗n⟩{n⟨y⃗⟩=N}{h′⟨x⃗n⟩=M}]
= [x⃗hk ⊢ M{n⟨y⃗⟩=N}]
= ϕσ⃗[x⃗kh ⊢ M{n⟨y⃗⟩=N}]
= ϕσ⃗ ([x⃗kn ⊢ M]; [y⃗h ⊢ N])

• ϕ_{A} : K(A ⊗ ¬B, C) ≅ K(A ⊗ ¬C, B) is natural in A, i.e. ϕ_{A′}(g ⊗ ¬B; f) =

g ⊗ ¬C; ϕ_{A}(f)

ϕσ⃗ ([x⃗n ⊢ M] ⊗ ¬τ⃗; [y⃗hk ⊢ N])
= ϕσ⃗ ([x⃗hn′ ⊢ M{n⟨y⃗⟩=n′⟨y⃗h⟩}]; [y⃗hk ⊢ N])
= ϕσ⃗[x⃗hk ⊢ M{n⟨y⃗⟩=n′⟨y⃗h⟩}{n′⟨y⃗h⟩=N}]
= [x⃗kh ⊢ M{n⟨y⃗⟩=N}]
= [x⃗kh ⊢ M{n⟨y⃗⟩=n′⟨y⃗k⟩}{n′⟨y⃗k⟩=N}]
= [x⃗kn′ ⊢ M{n⟨y⃗⟩=n′⟨y⃗k⟩}]; [y⃗kh ⊢ N]
= [x⃗n ⊢ M] ⊗ ¬τ′; ϕσ⃗[y⃗hk ⊢ N]

• (−) ⊗ σ⃗ is functorial

idσ⃗_{1} ⊗ σ⃗_{2}
= [x⃗k ⊢ k⟨x⃗⟩] ⊗ σ⃗
= [x⃗y⃗h ⊢ k⟨x⃗⟩{k⟨z⃗⟩=h⟨z⃗y⃗⟩}]
= [x⃗y⃗h ⊢ (h⟨z⃗y⃗⟩)[z⃗ ↦ x⃗]]
= [x⃗y⃗h ⊢ h⟨x⃗y⃗⟩]
= id_{σ⃗_{1}σ⃗_{2}}
= id_{σ⃗_{1}⊗σ⃗_{2}}

([x⃗n ⊢ M]; [y⃗k ⊢ N]) ⊗ σ⃗

= [x⃗k ⊢ M{n⟨y⃗⟩=N}] ⊗ σ⃗
= [x⃗w⃗h ⊢ M{n⟨y⃗⟩=N}{k⟨z⃗⟩=h⟨z⃗w⃗⟩}]
= [x⃗w⃗h ⊢ M{k⟨z⃗⟩=h⟨x⃗z⃗⟩}{n⟨y⃗⟩=N{k⟨z⃗⟩=h⟨z⃗w⃗⟩}}]
= [x⃗w⃗h ⊢ M{n⟨y⃗⟩=N{k⟨z⃗⟩=h⟨z⃗w⃗⟩}}]
= [x⃗w⃗k ⊢ M{n⟨y⃗⟩=n′⟨y⃗w⃗⟩}{n′⟨y⃗w⃗⟩=N{k⟨z⃗⟩=h⟨z⃗w⃗⟩}}]
= [x⃗w⃗n′ ⊢ M{n⟨y⃗⟩=n′⟨y⃗w⃗⟩}]; [y⃗w⃗h ⊢ N{k⟨z⃗⟩=h⟨z⃗w⃗⟩}]
= [x⃗n ⊢ M] ⊗ σ⃗; [y⃗k ⊢ M] ⊗ σ⃗

Analogously for σ⃗ ⊗ (−).

□

In [Fil89], a different notion of value was proposed: a morphism f : A ⟶ B is called total if it can be discarded in the sense that A ⟶ B ⟶ 1 = A ⟶ 1. This is plausible insofar as jumps cannot be discarded, so that by excluding jumps one might hope to isolate those program phrases that do not have control effects.

5.2.13 Proposition ⊗ is not a product in the subcategory of total morphisms.

Proof The following morphism twicecc is total (but not central). twicecc does not respect ⊗.

twicecc =^{def} [x⃗hk ⊢ k⟨x⃗l⟩{l⟨y⃗⟩=k⟨y⃗h⟩}] : A ⊗ ¬A ⟶ A ⊗ ¬A

Informally, in terms of continuation transformers, twicecc could be read as “k ↦ k ∘ k”.

This is total, because twicecc; [w⃗k ⊢ k⟨⟩] = [x⃗k ⊢ k⟨⟩] However, twicecc is not a value: it is too effectful to be copyable, in that twicecc; ⟨id, id⟩ ≠ ⟨twicecc, twicecc⟩ can be distinguished by ; [zcfz′c′gr ⊢ f⟨zq⟩{q⟨w⟩=g⟨wc⟩}].

The two composites are “h^{3}” as distinct from “h^{4}”, that is,

[x⃗ahk ⊢ h⟨x⃗q⟩{q⟨w⃗⟩=h⟨w⃗q⟩{q⟨w⃗⟩=h⟨w⃗a⟩}}]
[x⃗ahk ⊢ h⟨x⃗q⟩{q⟨w⃗⟩=h⟨w⃗q⟩{q⟨w⃗⟩=h⟨w⃗q⟩{q⟨w⃗⟩=h⟨w⃗a⟩}}}]

We omit the calculations here; this counterexample will be discussed in Chapter 6, where we demonstrate experimentally, by exhibiting programs, that twicecc cannot be copied. □

5.3 The indexed ¬ term model

We have mentioned in remark 4.4.1 during the discussion of ⊗¬-categories that it may be helpful to think about negation as indexed.

As this chapter establishes the connection between CPS calculus and categories, we now sketch how this indexed viewpoint is related to a two-zone CPS calculus (see remark 2.4.3), in which operations such as negation affect only a subset of the environment. We arrive at an indexed category as a term model of the alternative (indexed) view of ⊗¬-categories to be developed in chapter 8.

We define the equivalence of judgements as follows: x⃗:σ⃗, k:¬τ⃗ ⊢ M and x′:σ′, k′:¬τ′ ⊢ M′ are equivalent iff

M = M′[x′k′ ↦ x⃗k]

is derivable from the axioms of CPS calculus.

The indexed category H : C^{op} ⟶ Cat is defined as follows

• the objects of C and all the fibres H_{C} are sequnces of CPS type expressions

• the product of objects in C is given by concatenation of sequences

• a morphism from σ⃗ to τ⃗ in the base category C is an equivalence class of trivial judgements

y⃗ : σ⃗, q : ¬τ⃗ ⊢ q⟨p_{1} … p_{n}⟩{p_{i_{1}}⟨x⃗_{i_{1}}⟩=M_{i_{1}}} … {p_{i_{m}}⟨x⃗_{i_{m}}⟩=M_{i_{m}}}

• a morphism from τ⃗_{1} to τ⃗_{2} in Hσ⃗ is an equivalence class of judgements

z⃗ : σ⃗; x⃗ : τ⃗_{1}, k : ¬τ⃗_{2} ⊢ M

• the structure on the fibres is given as follows

id =^{def} [z⃗; x⃗k ⊢ k⟨x⃗⟩]
[z⃗; x⃗n ⊢ M]; [z⃗; y⃗k ⊢ N] =^{def} [z⃗; x⃗k ⊢ M{n⟨y⃗⟩=N}]
C^{∗}[z⃗; y⃗k ⊢ M] =^{def} [z⃗x⃗; y⃗k ⊢ M]
L[z⃗x⃗; w⃗k ⊢ M] =^{def} [z⃗; x⃗w⃗k′ ⊢ M{k⟨y⃗⟩=k′⟨x⃗y⃗⟩}]
κ_{C}[z⃗x⃗; w⃗k ⊢ M] =^{def} [z⃗; x⃗w⃗k ⊢ M]
¬[z⃗; x⃗k ⊢ M] =^{def} [z⃗; kh ⊢ h⟨f⟩{f⟨x⃗⟩=M}]
force =^{def} [z⃗; hk ⊢ h⟨k⟩]

• Reindexing on f along h: for f = [x⃗k ⊢ M] and

h = [y⃗q ⊢ q⟨p_{1} … p_{n}⟩{p_{i_{1}}⟨x⃗_{i_{1}}⟩=M_{i_{1}}} … {p_{i_{m}}⟨x⃗_{i_{m}}⟩=M_{i_{m}}}]

let

H_{h}(f) =^{def} [y⃗k ⊢ M{p_{i_{1}}⟨x⃗_{i_{1}}⟩=M_{i_{1}}} … {p_{i_{m}}⟨x⃗_{i_{m}}⟩=M_{i_{m}}}]

5.4 Recursion in CPS

We can define a term model K(RecCPS) of a ⊗¬-category analogously to definition 5.2.1, but using the recursive CPS calculus.

Recursion plays no part in any of the categorical structure of a ⊗¬-category, but we may ask what additional categorical structure it gives rise to. As a first step towards an answer to that, we sketch how a looping operator could be introduced categorically as a dinatural transformation. To some extent, this amount to a categorical account of “recursion from iteration” [Fil94a].

⦇μf.λx.M⦈(k) =^{def} k⟨f⟩{f⟨x⃗h⟩⇐⦇M⦈(h)}

A looping operator on a ⊗¬-category K is a dinatural transformation

fix_{A} : \xymatrix{K(A ⊗ ( ), ( )) \ar[r]^{··} & ZK(A, ¬( ))}

\xymatrix{ & K(A ⊗ C, C) \ar[r]^{^{fix^{A,C}}} & ZK(A, ¬C) \ar@{-->}[dr]^{ZK(A,¬C)} \\ K(A ⊗ B, C) \ar@{-->}[dr]_{K(A⊗B,g)} \ar@{-->}[ur]^{K(A⊗g,C)} &  &  & ZK(A, ¬C) \\  & K(A ⊗ B, B) \ar[r]^{_{fix_{A,B}}} & ZK(A, ¬B) \ar@{-->}[ur]_{ZK(A,¬g)}}

For \xymatrix{A ⊗ B \ar[r]^{f} & C} and \xymatrix{C \ar[r]^{g} & B}

fix_{A}(f; g); ¬g = fix_{A}(A ⊗ g; f)

5.4.1 Proposition In K = K(RecCPS), there is a looping operator

fix_{A} : \xymatrix{K(A ⊗ ( ), ( )) \ar[r]^{··} & Z(K)(A, ¬( ))}

given as follows

fixτ⃗[x⃗ : τ⃗, y⃗ : σ⃗, k : ¬σ⃗ ⊢ M] =^{def} [x⃗ : τ⃗, h : ¬¬σ⃗ ⊢ h⟨k⟩{k⟨y⃗⟩⇐M}]

Proof Let f =^{def} [x⃗ : τ⃗, y⃗n ⊢ M] and g =^{def} [z⃗k ⊢ N]. Then

fixτ⃗(f; g); ¬g
= fixτ⃗[x⃗q ⊢ h⟨k⟩{k⟨y⃗⟩⇐M{n⟨z⃗⟩⇐N}}]; [kq ⊢ q⟨f⟩{f⟨z⃗⟩⇐N}]
= [x⃗q ⊢ h⟨k⟩{k⟨y⃗⟩⇐M{n⟨z⃗⟩⇐N}}{h⟨k⟩⇐q⟨f⟩{f⟨z⃗⟩⇐N}}]
= [x⃗q ⊢ q⟨f⟩{f⟨z⃗⟩⇐N}{k⟨y⃗⟩⇐M{n⟨z⃗⟩⇐N}}]
fixτ⃗(τ⃗ ⊗ g; f)
= fixτ⃗[x⃗z⃗n ⊢ N{k⟨y⃗⟩⇐M}]
= [x⃗q ⊢ q⟨n⟩{n⟨z⃗⟩⇐N{k⟨y⃗⟩⇐M}}]

□

Our notion of equality for the recurive CPS calculus in not quite strong enough without some induction principle allowing us to conclude that

q⟨f⟩{f⟨z⃗⟩⇐N}{k⟨y⃗⟩⇐M{n⟨z⃗⟩⇐N}} = q⟨n⟩{n⟨z⃗⟩⇐N{k⟨y⃗⟩⇐M}}

as required for the dinaturality. Alternatively, we could construct the term model from term modulo observational congruence 2.3.2. We conjecture that for this notion of equality the dinnaturality would follow, i.e.:

q⟨f⟩{f⟨z⃗⟩⇐N}{k⟨y⃗⟩⇐M{n⟨z⃗⟩⇐N}} ≈ q⟨n⟩{n⟨z⃗⟩⇐N{k⟨y⃗⟩⇐M}}

5.4.1 Recursion from iteration

The point of having a looping construct for recursively-defined continuations is that, given our decomposition of functions into special continuations, it is exactly what is needed for recursively-defined functions. The link is established by the self-adjointness (on the left).

If M is the body of a recursively defined function f : σ → τ in the environment Γ, we have a judgement Γ, x : σ, f : (σ → τ) ⊢ M : τ whose denotation is a morphism

⟦M⟧ : ⟦Γ⟧ ⊗ ⟦σ⟧ ⊗ ¬(⟦σ⟧ ⊗ ¬⟦τ⟧) ⟶ ⟦τ⟧

applying the isomorphism of adjunction ϕ yields a morphism

ϕ_{⟦Γ⟧⊗⟦σ⟧} (⟦M⟧) : ⟦Γ⟧ ⊗ ⟦σ⟧ ⊗ ¬⟦τ⟧ ⟶ ⟦σ⟧ ⊗ ¬⟦τ⟧

and looping this gives a morphism

⟦Γ⟧ ⟶ ¬(⟦σ⟧ ⊗ ¬⟦τ⟧)

which is the denotation

⟦μf.λx.M⟧ : ⟦Γ⟧ ⟶ ⟦σ → τ⟧

This can be seen as a categorical distillation of Filinski’s “Recursion from iteration” [Fil94a], where ϕ was called a “context-switch”.

Effects in the presence of first-class continuations

In this chapter, we demonstrate that first-class continuations give rise to strong and rather subtle effects. First of all, this is an illustration and validation of our categorical semantics. The issue that we wish to clarify and give support to is our choice of the subcategory of effect-free computations.

While semantic considerations form the backdrop, it is also possible to read this chapter as an exploration of a simple idea: that the current continuation can be used twice. Once it is established that this can indeed be done, essentially the same idea leads to counterexamples to at least three separate conjectures:

• Andrzej Filinski’s view of the total morphisms as effect-free

• The idempotency hypothesis of Andrzej Filinski and Amr Sabry

• The decomposition of force (Felleisen’s C-operator), attempted by James Laird

Put more positvely, this shows that callcc is very expressive (in the sense of [Fel91]). The examples here seem to indicate that, intuitively or “morally”, firstclass continuations ought to be grouped together with state among the strong computational effects and not with much weaker effects like divergence.

Preliminaries

We make use of the categorical combinators that were defined in figure 1.12 on page 19 and figure 1.13 on page 20 for ML and Scheme, respectively.

We need to make a distinction between jumps in which the value thrown is not itself of continuation (or function) type, as in a plain goto like throwk(), and the unrestricted jumps afforded by callcc. We call the former first-order jump or exit, and the latter first-class jump.

6.1 Using the current continuation twice

One way of thinking about the denotations of terms in the presence of firstclass continuations is as “continuation transformers” transforming a continuation for their codomain backwards into a continuation for their domain, by analogy with predicate transformers transforming postconditions into preconditions. At first sight this appears to be dual to the usual functional way of thinking about denotations as transforming a value (of the type given by the domain of the denotation) forward into a result (of the type given by the codomain). However, this is not really a duality, because, although each value transformer gives rise to a continuation transformer by precomposition, not every continuation transformer arises this way. Non-standard manipulations of the control flow, as by control operators, do not simple apply the current continuation to a result. For instance, a jump typically ignores the current continuation. (Strictly speaking, this applies only to first-order jumps that do not pass the current continuation as an argument the way force does.) But there are other ways, apart from applying it or ignoring it, of transforming the current continuation: such as using it twice.

The identification of functions with certain continuations cuts both ways: not only can we reduce functions to continuations; we may also regard continuations of the appropriate type as functions and treat them accordingly.

In the context of the present discussion, this means that we can regard a computation of type

     '1a * '1a cont -> '1a * '1a cont

viewed (on the meta-level) as a continuation transformer

     ('1a * '1a cont) cont  -> ('1a * '1a cont) cont

as just a “function transformer” mapping the function space '1a -> 'a into itself. A fairly obvious candidate for such a function transformer is the function twice : f ↦ f ∘ f.

This continuation transformer counterpart of twice, while not representing a jump (ignoring the current continuation), is still a non-standard control manipulation, as it is different from applying the current continuation to a result.

6.1.1 Writing twicecc compositionally

We have two alternative ways of writing twicecc. We can take the CPS term

k⟨x⃗l⟩{l⟨y⃗⟩=k⟨y⃗h⟩}

fun twicecc (n,h) = callcc(fn k =>
                      (fn n => throw k (n,h))
                      (callcc(fn q => throw k (n,q))));
    twicecc : '1a * '1a cont -> '1a * '1a cont;

Figure 6.1: twicecc in continuation-grabbing style (nj-sml)

(define twicecc
  (lambda (l)
    (call/cc (lambda (f)
               ((lambda (n)
                  (f (list n (cadr l))))
                (call/cc (lambda (q)
                          (f (list (car l) q)))))))))

Figure 6.2: twicecc in continuation-grabbing style (Scheme)

fun twice f = f o f;
    twice : ('a -> 'a) -> ('a -> 'a);
fun twicecc a = (phi(funtocont o twice o conttofun) o thunk) a;
    twicecc :  '1a * '1a cont -> '1a * '1a cont;

Figure 6.3: twicecc in compositional style (nj-sml)

(define (twice f)
  (compose f f))
(define twicecc
  (compose
   (phi
    (compose fun-to-cont twice cont-to-fun))
   thunk))

Figure 6.4: twicecc in compositional style (Scheme) and do a continuation-grabbing style transform (see definition 2.6.8 on page 41) to arrive at an ML (and similarly, Scheme) program (figures 6.1 and 6.2).

This amounts to writing a continuation-passing style function composition in the source language (ML or Scheme), bypassing its control structure in favour of explicit jumps and continuation bindings. Although this method provides a practical use for continuation-grabbing style, it is somewhat rough and ready, in that it is a functional analogue of spaghetti coding and makes it harder for the compiler to supply useful type information for those subterms that are nonreturning.

Informally, we could paraphrase figures 6.1 and 6.2 as follows. The current continuation is seized by a callcc. It is then treated as a function by being composed with itself. This composition, though, is done in the style of CPS. That is to say, it the continuation treated as a function is invoked twice, each time with an argument and a result continuation. Composition is achieved by making the result continuation of the first invocation to be evaluated (textually this is the second one) refer to the place where the second one expects its argument. This is done by the inner callcc seizing the λ in the operator position as its continuation.

A more structured approach would be to start with the familiar function twice =^{def} λf.f ∘ f

twice : [A → A] ⟶ [A → A]

which up to coercion is a map

¬(A ⊗ ¬A) ⟶ ¬(A ⊗ ¬A)

Negating this and and composing with thunk and force yields

A ⊗ ¬A ⟶ ¬¬(A ⊗ ¬A) ⟶ ¬¬(A ⊗ ¬A) ⟶ A ⊗ ¬A

And this is what we do in figures 6.3 and 6.4, using the categorical combinators from figures 1.12 and 1.13. twicecc becomes a one-liner in ML, consisting mainly of function composition (with a η-redex to prevent non-generic weak type variable).

As a first illustration of what twicecc does, consider the following example in ML:

     callcc(fn k =>
            (fn (n, h) => throw h (n+1))
            (twicecc (0, k)));
     (* val it = 2 : int *)

Here the continuation of twicecc could be phrased as “pass the first argument incremented by one to the second argument”. The continuation that twicecc supplies to its arguments, then, is twice that; hence 0 is incremented twice before finally being passed to the (top-level) continuation supplied by the surrounding

callcc.

6.2 Copying and discarding

The fact that twicecc is total in the sense of discardable is corroborated by considering its composite with a function that discards its argument.

fun bang _= ();
    bang : '1a -> unit;
fun discardtester testee =
callcc(fn k => ((fn _=> 42) o testee)(0,k));

We demonstrate the fact that twicecc is not copyable (see proposition 5.2.13) by counterexample.

Copying twicecc (using copy twicecc) and attempting to copy its result after it has been run (using twicecc copy produce different results:

     - distinguisher copy_twicecc;
     val it = 3 : int
     - distinguisher twicecc_copy;
     val it = 4 : int

The context that can distinguish copy twicecc and twicecc copy, abstracted as distinguisher above, could be visualised as follows.

(\underbrace{I}_{0} ⊗ \underbrace{¬I}_{k})⊗\underbrace{¬(I ⊗ ¬I)}_{inc}⟶((\underbrace{I}_{n} ⊗ \underbrace{¬I}_{h})⊗\underbrace{¬(I ⊗ ¬I)}_{f})⊗((I⊗¬I)⊗\underbrace{¬(I ⊗ ¬I)}_{g})

We can show similarly that force is not copyable either (figures 6.2 and 6.8). force is in some sense maximally effectful: it is a jump, but as it passes the current continuation as an argument, it is more sensitive than an ordinary jump, which is oblivious to its current continuation.

The distinguishing context to show that force is not copyable is built using the one for twicecc. Roughly speaking, (force, force) will behave like (twicecc,twicecc) when each occurrence of force is given a separate copy of twicecc wrapped in a thunk.

fun copy_twicecc x = (twicecc x, twicecc x);
fun twicecc_copy x = (fn y => (y,y)) (twicecc x);
fun distinguisher testee =
    callcc(fn k =>
           (fn (((n,h),f),(_,g)) =>
            throw h (conttofun f (conttofun g  n)))
           (testee ((0,k),funtocont (fn n => n+1))));

Figure 6.5: Effectfulness of twicecc. Copying a computation, copying its result and a context to distinguish them (nj-sml)

(define copy-twicecc
  (lambda (l)
      (list (twicecc l) (twicecc l))))
(define twicecc-copy
  (lambda (l)
    ((lambda (y) (list y y))
     (twicecc l))))
(define distinguisher
  (lambda (testee)
    (call/cc (lambda (k)
               ((lambda (l)
                  ((cadaar l)
                   ((cont-to-fun (cadadr l))
                    ((cont-to-fun (cadar l))
                     (caaar l)))))
               (testee
                (list (list 0 k)
                      (fun-to-cont
                       (lambda (n) (+ n 1))))))))))

Figure 6.6: Effectfulness of twicecc. Copying a computation, copying its result and a context to distinguish them (Scheme)

fun copyforce h = (force h, force h);
fun forcecopy h = (fn a => (a,a)) (force h);
fun distinguisher2 f =  distinguisher (f o (delay twicecc));

Figure 6.7: force is not copyable (nj-sml)

(define copy-force
  (lambda (h)
    (list (call/cc h) (call/cc h))))
(define force-copy
  (lambda (h)
    ((lambda (y) (list y y))
     (call/cc h))))
(define (distinguisher2 testee)
  (distinguisher
   (compose
    testee
    (negate (negate twicecc))
    thunk)))

Figure 6.8: force = call/cc is not copyable (Scheme)

6.2.1 twicecc is not thunkable

This also provides an example for the fact that twicecc is not thunkable in the sense that composing with thunk does not succeed in wrapping the computation of twicecc into a thunk. Hence the following simple-minded attempt to define the distinguishing context for the non-copyability of force does not work:

     fun forcecopytesterwrong f =
         distinguisher (f o (pseudodelay twicecc));

Because forcecopytesterwrong cannot pass twicecc to each occurrence of force, both tests evaluate to the same value, 4.

     - forcecopytesterwrong copyforce;
     val it = 4 : int
     - forcecopytesterwrong forcecopy;
     val it = 4 : int

A proper distinguishing context uses the delaying idiom , which negates twicecc.

     fun forcecopytester f =
         distinguisher (f o (delay twicecc));

Now the non-copyablity of force manifests itself in the same way as for twicecc

     - forcecopytester copyforce;
     val it = 3 : int
     - forcecopytester forcecopy;
     val it = 4 : int

6.2.2 Cancellable and copyable are orthogonal

Considering that values are copyable and discardable whereas jumps (throw) are copyable but not discardable, we can summarise that copyable and discardable are orthogonal.

While it is evident that values can be discarded and jumps cannot, the right column was previously thought to be unoccupied, in that Filinski [Fil89] thought that cancellability, separating the top from the bottom row, was sufficient for separating value from all effects.

copyable not copyable
discardable x λx.M twicecc a

              not discardable throw k 42    force h

A corollary of this table is that a first-order jump like throw 42 is not maximally effectful. When it comes to being effectful, it is self-defeating in that it forgets the current continuation. This implies that is it copyable, because one jump (λx.(x, x))(throw k 42) is as good as two (throw k 42, throw k 42), because the first jump will ignore it continuation containing the second jump, so that it does not matter whether the latter is present or not. The quintessential first-class jump force, by contrast, is not oblivious to its continuation, as this is passed as an argument. This makes force sufficiently sensitive to its continuation to resist copying.

6.2.3 First-class control is not an idempotent effect

distinguisher also gives us a counterexample to the conjecture, due to Andrzej Filinski and Amr Sabry, that control is an idempotent effect; thanks to Andrzej Filinski for pointing this out to me. [Andrzej Filinski, personal communication].

The idempotency conjecture holds that (λx.(x, x))M should be indistinguishable from (M, M)

The conjecture could be perhaps be supported by informal arguments about first-order jumps. We have mentioned that these can be copied essentially because they are oblivious to their continuation, so that it does not matter if another jump follows. Hence the idempotency could be defended for values, as well as for firstorder jumps. What it fails to take into account are terms that do not simply pass something to the current continuation, but do not ignore it either. There seems to be an assumption of a kind of excluded middle here, along the line of: functions in continuations semantics can return a value or else they are goto’s.

As witnessed by twicecc, first-class continuations are more subtle than that. Continuations of the appropriate type can be used just as ordinary function declared with a fun or define.

To refute the idempotency hypothesis, we once again use distinguisher and

twicecc:
     distinguisher (fn x => (fn y => (y,y)) (twicecc x));
     distinguisher (fn x => (fn a => (fn b => (a,b))
                  (twicecc x)) (twicecc x));

To the extent that that such ideological conclusions can be drawn from this example, we should like to argue that it is misleading to think of first-class control as a form of non-termination due to jumping.

6.3 Centrality and effect-freeness

Having demonstrated in section 6.2 that discardable morphisms are too permissive a notion to be a suitable characterization of effect-free computation, we now try to add some plausibility to the claim that centrality in the presence of first-class continuations is a suitable notion.

We mentioned in remark 4.4.2 that it is due to the self-adjointness that centrality can be assumed to imply effect-freeness. There is some room for misunderstanding here, as there is a different, but weaker, argument for such an implication. We hope to clarify the connection between centrality and effect-freeness in the presence of first-class continuations by some concrete examples

First note that we can talk about centrality in quite a general setting: whenever we have a language having a let- and a tuple construct, we can define a term M to be central iff for all fresh variables a and b and all other terms N,

let a = M in let b = N in (a, b)

is the same (under whatever notion of equality we happen to have) as

let b = N in let a = M in (a, b)

For instance, if our notion of effect is given by (not necessarily first-class) continuations and at least two different values that can be thrown, then terms M that throw cannot be central. We only need to take for N a term that throws something else in order to tell the difference between the two composites.

     -(callcc (fn k =>
     let val a = throw k "A side effect." in
     let val b = throw k "A subtly different side effect.\n" in
            a  end end));
     = = = val it = "A side effect." : string
     - (callcc (fn k =>
     let val b = throw k  "A subtly different side effect." in
     let val a = throw k  "A side effect." in
            a  end end));
     = = = val it = "A subtly different side effect." : string

If additional side-effects, such as input-output, are present in the language, it is quite straightforward to see that twicecc is not central; see figure 6.11. Some more care is needed if control is the only effect.

fun forcefirst (a,b) =
    let val y = force b in
    let val x =  (output(std_out, "A side effect.\n"); 42) in
        (x,y) end end;
fun forcelast (a,b) =
    let val x =  (output(std_out, "A side effect.\n"); 42) in
    let val y = force b in
        (x,y) end end;
fun trytoreify f (n,k) =
    (phi(fn h  =>
         (fn (x,y) => throw y x)
         (f ((n,k),h)))) (thunk ());
val effectnotinclosure = trytoreify forcelast ((),());
    effectnotinclosure  : int cont cont;

val effectinclosure = trytoreify forcefirst ((),());
    effectinclosure  : int cont cont;
force effectnotinclosure;
force effectinclosure;

Figure 6.9: force can reify by being precomposed (in ML)

(define (forcelast l)
  (let*
      ((x (begin (write "A side effect.") (newline) 42))
       (y (call/cc (cadr l))))
    (list x y)))
(define (forcefirst l)
  (let*
      ((y (call/cc (cadr l)))
       (x (begin (write "A side effect.") (newline) 42)))
    (list x y)))
(define (trytoreify f)
  (lambda (l)
    ((phi
      (lambda (h)
        ((lambda (l) ((cadr l) (car l)))
         (f (list l h)))))
     (thunk (list)))))
(define effectnotinclosure ((trytoreify forcelast) (list)))
(define effectinclosure ((trytoreify forcefirst) (list))))
(call/cc effectnotinclosure)
(call/cc effectinclosure)

Figure 6.10: force can reify by being precomposed (in Scheme)

However, with first-class continuations, one can do much more than subject M to testing for effects; one can actually reify M. For N = force h, the composite with force coming after M

                let a = M in let b = force h in (a, b)

passes to h the continuation after running M; this gives access to the value that M returns after being run and possibly side-effecting. The composite with force coming first, by contrast, passes to h the continuation before M is computed. This has the same effect as wrapping the whole computation, include possible side-effects, into a thunk.

So instead of the somewhat weak argument “if M had effects, we should be able to find a test N that can tell the difference”, we know that force will reify anything that follows. Now, intuitively speaking, in order for the two composites to agree, (i.e. for M thunked and unthunked to be the same) M itself must already be as good as reified.

Depending on whether force appears first or not, one can achieve either the genuine thunking

\xymatrix{A \ar[r]^{thunk} & ¬¬A \ar[r]^{¬¬f} & ¬¬B}

or the “pseudo-delaying”

\xymatrix{A \ar[r]^{f} & B \ar[r]^{thunk} & ¬¬B}

The difference between these two is demonstrated in figures 6.9 and 6.10.

6.3.1 twicecc is not central

We established in 6.2 that twicecc cannot be copied. In our semantics, central morphisms respect the product and can be copied. Thus twicecc cannot be central — at least that is what the semantics predicts.

To show that this is indeed the case, and so to validate our semantics, we consider a final experiment.

As in the above, counterexamples are easier to find if we allow ourselves the additional observations afforded by I/O) — see figure 6.11 for a demonstration that twicecc is not central.

For the general case, without relying on I/O, we reuse the distinguishing context once more. The fact that twicecc is not central is demonstrated in figures 6.12 and 6.13.

callcc(fn k =>
       (fn (((),x),y) => throw x y)
        (let val y = output(std_out, "Side effect.\n") in
         let val x = twicecc ((),k) in
             (x,y) end end));
(* prints once *)
callcc(fn k =>
       (fn (((),x),y) => throw x y)
       (let val x = twicecc ((),k) in
        let val y = output(std_out, "Side effect.\n") in
            (x,y) end end));
(* prints twice *)

Figure 6.11: twicecc is not central (shown using I/O)

fun twicecc_first (a,b) =
       let val x = twicecc a in
       let val y = force b in
           (x,y) end end;
fun twicecc_last (a,b) =
       let val y = force b in
       let val x = twicecc a in
           (x,y) end end;
fun centralitytester testee =
    distinguisher ((fn h => (force h, force h))
                   o (trytoreify testee));
centralitytester twicecc_last;  (* val it = 3 : int *)
centralitytester twicecc_first; (* val it = 4 : int *)

Figure 6.12: twicecc is not central (In ML)

(define (twicecc_first l)
  (let*
      ((x (twicecc (car l)))
       (y (call/cc (cadr l))))
    (list x y)))
(define (twicecc_last l)
  (let*
     ((y (call/cc (cadr l)))
      (x (twicecc (car l))))
    (list x y)))
(define (centralitytester testee)
  (distinguisher
   (compose
    (lambda (h) (list (call/cc h) (call/cc h)))
    (trytoreify testee))))
(centralitytester twicecc_last)
(centralitytester twicecc_first)

Figure 6.13: twicecc is not central (In Scheme)

6.4 Another non-copyability result

We consider another morphism that is total, but not copyable, while easier to understand in intuituive terms than twicecc. We will also need it for the construction of counterexamples in Sections 6.5 and 6.7 below.

Written as a CPS term, twicecc seems to be the simples way of using the current continuation twice.

[x⃗hk ⊢ k⟨xq⟩{q⟨y⃗⟩=k⟨y⃗h⟩}]

If we want something of function type using its current continuation twice, we can write the following CPS term in the same spirit as twicecc, though slightly longer:

[k ⊢ k⟨f⟩{f⟨xp⟩=k⟨f⟩{f⟨yq⟩=q⟨x⟩}}]

Despite being longer as a CPS term, this is easier to write in ML or Scheme, requiring no messy Continuation Grabbing Style:

callcc(fn k => throw k (fn x => throw k (fn y => x)))}

This term passes a function to its current continuation k. When this function is called with an argument x, the constant function always returning that argument is passed to k. Hence the function eventually (on the second call to the current continuation) returned by the term is the function always returning the argument to the first call. We can regard this as the solution to the following continuation programming exercise:

Define a function f such that all calls to f return the argument of the first call of f. Do not use state.

(We name this argfc, for “argument of first call”.)

At first sight, it seems hard to see how to do this without state: the obvious solution, after all, uses two variables (or in ML, references): a non-local variable to hold the argument of the first invocation and a boolean flag to record if the function has been called before (if not, then the variable needs to be assigned). See figures 6.16 and 6.17 for a version of argfc with local state. In Scheme (figure 6.17), we can give a better analogue of argfc with continuations (figure 6.15) by using a function that updates its own definition when it is called.

fun argfc () =
    callcc(fn k =>
           throw k (fn x => throw k (fn y => x)));
let val f = argfc () in
    [f 1, f 2, f 3, f 4] end;
let val f = argfc () in
    [f 42, f 2, f 3, f 4] end;
fun distinguisher testee =
    let val (f, g) = testee argfc () in
        (f 1, g 2)
        end;
distinguisher (fn f => fn x => (f x, f x));
(* (1,2) : int * int *)
distinguisher (fn f => fn x => ((fn y => (y,y)) (f x)));
(*  (1,1) : int * int *)

Figure 6.14: argfc cannot be copied (in ML)

(define argfc
  (lambda ()
    (call/cc
     (lambda (k)
       (k
        (lambda (x)
          (k
           (lambda (y) x))))))))

(let ((f (argfc)))
  (list (f 1) (f 2) (f 3) (f 4)))
; a list with the all entries the same (1,2,3 or 4); unspecified which
; bigloo picks the last
(let ((f (argfc)))
  (list (f 5647) (f 3425) (f 2484) (f 75473)))
; Cannot be copied
(define (distinguisher testee)
  ((lambda (l)
     ((car l) 1)
     ((cadr l) 2))
   ((testee argfc))))
(distinguisher
 (lambda (f)
   (lambda ()
     (list (f) (f)))))              ;  2
(distinguisher
 (lambda (f)
   (lambda ()
     ((lambda (y) (list y y))       ;  1
      (f)))))

Figure 6.15: argfc cannot be copied (in Scheme)

fun argfc () =
    let val fc = ref true
        and arg = ref 0
    in
        fn x =>
        (if !fc then (fc := false; arg := x)
         else ();
             !arg)
    end;

Figure 6.16: argfc with local state (in ML)

; use local variable for argument of first call
(define (argfc)
  (let
      ((fc #t)
       (arg 0))
    (lambda (x)
      (if fc
          (begin
            (set! fc #f)
            (set! arg x)))
      arg)))
; use variable for the function
(define (argfc)
  (letrec
      ((fc #t)
       (f (lambda (x)
            (if fc
                (begin
                  (set! fc #f)
                  (set! f (lambda (y) x))))
            (f x))))
    f))

Figure 6.17: argfc with local state (in Scheme)

datatype void = VOID of void;
fun invoid (VOID x) =  invoid x;
fun callcc' f = callcc(fn k => f (fn x => VOID(throw k x)));
    callcc' :  (('1a -> void) -> '1a) -> '1a;

Figure 6.18: Variant of callcc with void-returning continuations

6.5 The failure of Laird’s bootstrapping of force

In [Lai97], James Laird claims that control operators at ground type are sufficient in that one can inductively define them at function types. Specifically, he gives an inductive definition for the double-negation control operator of type

     ((('a -> '2b) -> void) -> void) -> 'a -> '2b;

in terms of that of type

(('2b -> void) -> void)  -> '2b)

[James Laird, personal communication/email].

For ML, the inductive definition supposes a variant of callcc in which continuations are identified with functions … − > void; see figure 6.18.

In figure 6.19, we give a simplistic version laird1 first; the function laird can then be seen as a refinement thereof designed to cope with control effects in its argument. The Scheme analogue is in figure 6.20.

The informal argument for the correctness of this construction seems essentially similar to that which one could advance in favour of the idempotency hypothesis. The function laird, in its improved version, first gives its argument h a chance to side-effect by jumping out of the current evaluation. If h did not jump, it can then be treated like a value.

Implicit in this reasoning, one can find the assumption that all total morphisms are effect-free. Again, it is refuted by using the current continuation twice: figures 6.21 and 6.22.

The importance of this refutation lies in that it invalidates Laird’s claim to have a fully abstract semantics for a language with callcc [Lai97]. Hence the situation for full abstraction is not improved by Games models. There still is a tradeoff between models with the full callcc, but not fully abstract without the

fun laird1 (h :(('a -> '2b) -> void) -> void) =
fn x =>
(force' : (('2b -> void) -> void)  -> '2b)
(fn y => h ((fn z => y (z x))));
laird1 : ((('a -> '2b) -> void) -> void) -> 'a -> '2b;
fun laird  h =
(fn u =>
 fn x => force' (fn k =>
                 h (fn f => k (f x))))
(force' (fn p => h (fn f => p (fn z => z))));
laird  : ((('a -> '2b) -> void) -> void) -> 'a -> '2b;

Figure 6.19: Laird’s bootstrap in ML

(define (laird h)
((lambda (u)
 (lambda (x)
   (call/cc (lambda (k)
              (h (lambda (f) (k (f x))))))))
(call/cc (lambda (p)
          (h (lambda (f) (p (lambda (z) z))))))))

Figure 6.20: Laird’s bootstrap in Scheme

val argfct =
    callcc'(fn a =>
            (fn k =>
             invoid (k (fn x =>
                        invoid (k (fn y => x: string)))))
            (force' a));
    argfct : ((string -> string) -> void) -> void;

fun lairddistinguisher testee =
    (fn f =>
       (f "Not Laird.";
        f "Laird."))
    (testee argfct);
lairddistinguisher force';
lairddistinguisher laird;

Figure 6.21: Failure of Laird’s bootstrap: A distinguishing context in ML

(define argfct
  (call/cc (lambda (a)
             ((lambda (k)
                (k (lambda (x) (k (lambda (y) x)))))
              (call/cc a)))))
(define (lairddistinguisher testee)
  ((lambda (f)
     (f "Not Laird.")
     (f "Laird."))
    (testee argfct)))
(lairddistinguisher call/cc)
(lairddistinguisher laird)

Figure 6.22: Failure of Laird’s bootstrap: A distinguishing context in Scheme prompt [SF90], and fully abstract models with callcc only at base types, as in [KCF92].

A possible formal connection between the refutation of Laird’s attempt at bootstrapping force and the categorical approach appears to be given by coherence conditions, or more specifically lack thereof. Whereas the unit of the self-adjointness on the right satisfies coherence, the unit for the self-adjointness on the left seems to be inherently indecomposable. Intuitively, it seems evident (in the light of the counterexample) that force at function type σ → τ needs to pass the continuation of type ¬(σ → τ) to its argument and cannot get away with passing something else fabricated from a continuation of type ¬τ. It may be an open problem meriting futher work to state in precisely which algebraic sense force is indecomposable (or possibly prime).

6.6 Cross reference to preceding chapters

The programs in this chapter are intended to illustrate a (semantic) point. This relates then to material in other chapters. We give a little link table.

Slogan  ML code Scheme code Proposition or remark
twicecc not copyable figure 6.5 figure 6.6  5.2.13
force can reify figure 6.9 figure 6.10 4.4.2
twicecc not central figure 6.12 figure 6.13  implied by 5.2.7

6.7 Discriminating λx.xx and λx.x(λy.xy) under call by name

We show that the expressive power of callcc is sufficient to distinguish the terms λx.xx and λx.x(λy.xy) under call-by-name. Moreover, the key ingredient for making the distinction is a term that uses its current continuation twice.

Recall the Plotkin call-by-name CPS transform from Definition 3.2.2: \underline{( )} extended with callcc and throw.

\underline{x} = x⟨k⟩
\underline{λx.M} = k⟨f⟩\{f⟨xk⟩=\underline{M}\}
\underline{MN} = \underline{M}\{k⟨f⟩=f⟨nk⟩\{n⟨k⟩=\underline{N}\}\}
\underline{callcc M} = \underline{M}\{k⟨f⟩=f⟨gk⟩\{g⟨p⟩=p⟨k⟩\}\}
\underline{throw M N} = \underline{M}\{k⟨k⟩=\underline{N}\}

Here we consider this as an untyped transform from untyped λ-calculus (with callcc) to untyped CPS calculus.

We define a source language term argfc by

argfc =^{def} callcc(λh.throw h (λx.throw h (λy.x)))

Let A(k) =^{def} \underline{argfc} be the corresponding CPS term (for current continuation k), that is to say:

       \underline{argfc}

=

callcc(λh.throw h (λx.throw h (λy.x)))
= h⟨k⟩{k⟨k⟩=k⟨f⟩{f⟨xk⟩=h⟨k⟩{k⟨k⟩=k⟨f⟩{f⟨yk⟩=x⟨k⟩}}}}{h⟨p⟩=p⟨k⟩}
= k⟨f⟩{f⟨ap⟩=k⟨f⟩{f⟨bq⟩=a⟨q⟩}}
=^{def} A(k)

First, we note that A(k){k⟨f⟩=f⟨yk⟩} = y⟨k⟩.

A(k){k⟨f⟩=f⟨yk⟩}
= k⟨f⟩{f⟨ap⟩=k⟨f⟩{f⟨bq⟩=a⟨q⟩}}{k⟨f⟩=f⟨yk⟩}
= f⟨yk⟩{f⟨ap⟩=f⟨yk⟩{f⟨bq⟩=a⟨q⟩}}
= y⟨k⟩

Now the CPS transform of xx is:

\underline{xx} = x⟨k⟩\{k⟨f⟩=f⟨xk⟩\}

Therefore,

\underline{(λx.xx) argfc}
= k⟨f⟩\{f⟨xk⟩=\underline{xx}\}\{k⟨f⟩=f⟨xk⟩\{x⟨k⟩=A(k)\}\}
= \underline{xx}\{x⟨k⟩=A(k)\}
= x⟨k⟩{k⟨f⟩=f⟨xk⟩}{x⟨k⟩=A(k)}
= A(k){k⟨f⟩=f⟨xk⟩{x⟨k⟩=A(k)}}
= A(k){k⟨f⟩=f⟨xk⟩}{x⟨k⟩=A(k)}
= x⟨k⟩{x⟨k⟩=A(k)}
= A(k)

On the other hand, the CPS transform of x(λy.xy) is:

\underline{x(λy.xy)} = x⟨k⟩\{k⟨f⟩=f⟨nk⟩\{n⟨k⟩=k⟨g⟩\{g⟨yk⟩=x⟨k⟩\{k⟨f⟩=f⟨yk⟩\}\}\}\}

Hence,

\underline{(λx.x(λy.xy)) argfc}

= k⟨f⟩\{f⟨xk⟩=\underline{x(λy.xy)}\}\{k⟨f⟩=f⟨xk⟩\{x⟨k⟩=A(k)\}\}
= \underline{x(λy.xy)}\{x⟨k⟩=A(k)\}
= x⟨k⟩{k⟨f⟩=f⟨nk⟩{n⟨k⟩=k⟨g⟩{g⟨yk⟩=x⟨k⟩{k⟨f⟩=f⟨yk⟩}}}}{x⟨k⟩=A(k)}
= A(k){k⟨f⟩=f⟨nk⟩{n⟨k⟩=k⟨g⟩{g⟨yk⟩=A(k){k⟨f⟩=f⟨yk⟩}}}}
= A(k){k⟨f⟩=f⟨nk⟩}{n⟨k⟩=k⟨g⟩{g⟨yk⟩=A(k){k⟨f⟩=f⟨yk⟩}}}
= n⟨k⟩{n⟨k⟩=k⟨g⟩{g⟨yk⟩=y⟨k⟩}}
= k⟨g⟩{g⟨yk⟩=y⟨k⟩}

Finally, A(k) and k⟨g⟩{g⟨yk⟩=y⟨k⟩} can be distinguished.

The terms λx.xx and λx.(λy.xy) are one of the canonical examples cited as evidence of the expressive power of the π-calculus [San95]. While it was originally claimed that this expressive power was due to specific features of the π-calculus, notably nondeterminism, the realisation of the importance of CPS in the translation form λ to π-calculus makes it seem plausible that it is in fact due to the presence of continuations [Davide Sangiorgi, personal communication]. The above can be seen as preliminary evidence of this view. It is perhaps not surprising that we can distinguish λx.xx and λx.x(λy.xy), in that the same terms were already used in [Plo75] as a counterexample to show a non-completeness result.

Compare this with Theorem 8.5 and the “conditional η-rule” Corollary 8.4 in [San94]. There Sangiorgi shows that in a Church-Rosser calculus, λx.xx and λx.x(λy.xy) cannot be distinguished. Roughly, the reason is that in case M diverges, both MM and M(λy.My) diverge; otherwise M and λy.My (y fresh) cannot be distinguished.

This reasoning, valid for a restricted class of calculi, appears to be precisely what gave rise to the flawed assumptions about control operators encountered in the preceding sections.

In the remainder of this section, we formualise the distinction between λx.xx and λx.x(λy.xy) in Scheme. In order to make Scheme behave like call-by-name, we use a thunking transform, with the thunks being implemented by means of continuations. Concretely, this means that variable occurences need to be replaced by forcings and function arguments need to be thunked. The first is achieved by replacing x with (call/cc x) Recall that in Scheme, forcing a CPS thunk is just a special case of call/cc. (This is not necessarily the same as Scheme’s built in force, which may or may not be implemented in this way.) In the two λ-terms and λx.x(λy.xy), the only arguments are values, hence we can thunk them by applying then function thunk, and if the argument is a variable that cancels its forcing, i.e.(thunk(force x))= x, so we can just write x.

Notice that the call-by-name semantics of argfc is the same as the call-by-value semantics of an almost identical term.

                callcc(λh.throw h (λx.throw h (λy.force x)))

=

callcc(λh.throw h (λx.throw h (λy.x)))

So in Scheme, the distinguishing context will consist essentially of a (call by name) application to callcc(λh.throw h (λx.throw h (λy.force x))).

For writing the distinguishing context in figure 6.23, we need to take some care in thunking term that are not values. For thunking a value, we can simply apply the function thunk:

(define (thunk a)
  (call/cc
   (lambda (k)
     ((call/cc k)
      a))))

However, applying thunk to a side-effecting term does not succeed in wrapping the side effect into the thunk. In (thunk (write 'Effect)), the argument will be evaluated an the effect will not be wrapped into the thunk. That can only be achieved by putting the side-effecting term into the thunking idiom:

(define effect-in-thunk
  (call/cc
   (lambda (q)
     ((lambda (p)
        (p (write 'Effect)))
      (call/cc q)))))

The printing occurs only when (call/cc effect-in-thunk) is forced.

More generally, a computation, as opposed to a value, is thunked by the following idiom:

(call/cc
 (lambda (q)
   ((lambda (p)
      (p

(define lambda-x-xx
  (lambda (x)
    ((call/cc x)
     x)))
(define lambda-x-x-lambda-y-xy
  (lambda (x)
    ((call/cc x)
     (thunk
      (lambda (y)
        ((call/cc x)
         y))))))
(define dist
  (lambda (testee)
    ((lambda (f)
       (f (thunk 1))
       (f (thunk 2)))
     (testee
      (call/cc
       (lambda (q)
         ((lambda (p)
            (p
             (call/cc
              (lambda (h)
                (h
                 (lambda (x)
                   (h
                    (lambda (y)
                      (call/cc x)))))))))
          (call/cc q))))))))

Figure 6.23: Distinguishing λx.xx and λx.x(λy.xy) under call by name computation to be thunked

       ))
    (call/cc q))))

In figure 6.23, we put all these ingredients, namely, (a variant of) the function argfc, the thunking idiom, and a distinguishing context like that in figure 6.15, together to get a distinguishing context for λx.xx and λx.x(λy.xy) under call by name.

   (dist lambda-x-xx) evaluates to 1, while (dist lambda-x-x-lambda-y-xy)

evaluates to 2.

Categorical semantics in ⊗¬-categories

In this chapter, we develop the categorical counterparts of CPS transforms (see chapter 3).

7.1 Call-by-value semantics

Given the notions of λ-abstraction from section 4.5, a simply-typed λ-calculus can be interpreted in a ⊗¬-category.

For call-by-value, control operators are naturally part of such a semantics, as they relate directly to the fundamental operations on the ¬ type. Specifically, callcc is interpreted as post-composition with the adjoint correspondent

[¬A → A] = ¬(¬A ⊗ ¬A) ⟶ A

of the diagonal map ¬A ⟶ ¬A ⊗ ¬A.

7.1.1 Definition (Semantics for call-by-value with callcc ) Given a ⊗¬- category K, we can give an interpretation V⟦−⟧ for λ-calculus with control as follows. Types and environments are interpreted as usual, except for the breaking down of arrow types.

V⟦¬τ⟧ =^{def} ¬V⟦τ⟧
V⟦σ → τ⟧ =^{def} ¬(V⟦σ⟧ ⊗ ¬V⟦τ⟧)
V⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ =^{def} V⟦τ_{1}⟧ ⊗ ⋯ ⊗ V⟦τ_{n}⟧

A judgement Γ ⊢ M : τ denotes a morphism V⟦Γ⟧ ⟶ V⟦τ⟧, defined by induction on M.

V⟦x_{1}:τ_{1}, …, x_{n}:τ_{n} ⊢ x_{j}:τ_{j}⟧ =^{def} π_{j}

V⟦Γ ⊢ λx.M : σ → τ⟧ =^{def} \overline{λ}_{V⟦σ⟧} V⟦x : σ, Γ ⊢ M : τ⟧
V⟦Γ ⊢ throw M N:σ⟧ =^{def} ⟨V⟦Γ ⊢ N:τ⟧, V⟦Γ ⊢ M:¬τ⟧⟩; V⟦τ⟧ ⊗ ¬π_{1}; apply
V⟦Γ ⊢ callcc M : τ⟧ =^{def} V⟦Γ ⊢ M:¬τ → τ⟧; ¬⟨id_{¬V⟦τ⟧}, id_{¬V⟦τ⟧}⟩; force
V⟦Γ ⊢ MN : τ⟧ =^{def} ⟨V⟦Γ ⊢ N : σ⟧, V⟦Γ ⊢ M : σ → τ⟧⟩; apply

What makes the call-by-value semantics work is the fact that all values denote central morphisms, together with the fact that the centre has finite products and that we have central closure.

7.1.1 The naturality of callcc

Perhaps the most canonical property of control operators is the naturality of callcc, in the sense of the following axiom from [Hof94].

V (callcc M) = callcc(λk.V (M(λx.k (V x))))

where V ranges over values, i.e. V ::= x | λx.M. However, this relies on continuations being a special case of procedures, as in Scheme. With a typing for continuations like that in nj-sml, instances of this axiom will be ill-typed.

The negation operation suggested by our categorical semantics, definable as negate =^{def} λf.λh.callcc((throw h) ∘ f ∘ callcc ∘ throw), is useful for adapting this axiom as follows

             V (callcc M) = callcc(V ∘ M ∘ (negate V))

(For example, let V = fn n => n + 1 and M = fn k => throw k 1. Then V (callcc M) and callcc(V o M o (negate V)) both evaluate to 2.) This axiom is sound for our semantics.

7.1.2 Proposition

V⟦Γ ⊢ V (callcc M) : τ⟧ = V⟦Γ ⊢ callcc(V ∘ M ∘ (negate V)) : τ⟧

7.2 Plotkin call-by-name semantics and variants

Plotkin call-by-name is a slight variation on call-by-name obtained by the technique of thunking arguments in applications and forcing variables; this is a categorical analogue of [HD95].

We consider the doubly-negated ¬¬A as the type of lazy data of type A.

7.2.1 Definition (Plotkin Call-by-name semantics) Given a ⊗¬-category, we define the Plotkin call-by-name semantics L⟦−⟧ as follows.

L⟦σ → τ⟧ =^{def} ¬(¬¬P⟦σ⟧ ⊗ ¬P⟦τ⟧)
L⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ =^{def} ¬¬P⟦τ_{1}⟧ ⊗ ⋯ ⊗ ¬¬P⟦τ_{n}⟧

Again, a judgement Γ ⊢ M : τ denotes a morphism P⟦Γ⟧ ⟶ P⟦τ⟧.

P⟦x_{1}:τ_{1}, …, x_{n}:τ_{n} ⊢ x_{j}:τ_{j}⟧ =^{def} π_{j}; force
P⟦Γ ⊢ λx.M : σ → τ⟧ =^{def} \overline{λ}_{P⟦σ⟧}P⟦x : σ, Γ ⊢ M : τ⟧
P⟦Γ ⊢ MN : τ⟧ =^{def} ⟨\overline{λ}_{1} P⟦Γ ⊢ N : σ⟧, P⟦Γ ⊢ M : σ → τ⟧⟩; apply

The Plotkin call-by-name semantics satisfies the full β law by thunking arguments at the point of application. Thus arguments are always central. However, the application map is still the same as in call-by-value; the “jump with arguments”, apply, which is not central.

The Plotkin semantics rests on the thunking corollary 4.5.3 along with the central closure 4.5.2.

7.2.2 Remark We now have the categorical framework in place in order to talk more abstractly about some of the issues addressed in terms of name-passing in chapter 3. We mentioned in remark 3.1.5 two possible choices for a semantics with the call-by-name typing. These correspond to the two passages in the naturality square for thunk. Because the naturality does not hold in general (only in a subcategory), there really is a choice.

As explained in [DH94], the Reynolds and modified Reynolds have the same semantics of function types, but they differ in the choice of when the delayed argument is forced.

7.2.3 Definition Given a ⊗¬-category, we define the Reynolds call-by-value semantics R⟦−⟧ as follows.

R⟦σ → τ⟧ =^{def} ¬(¬¬R⟦σ⟧ ⊗ ¬R⟦τ⟧)
R⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ =^{def} ¬¬R⟦τ_{1}⟧ ⊗ ⋯ ⊗ ¬¬R⟦τ_{n}⟧

A judgement Γ ⊢ M : τ denotes a morphism R⟦Γ⟧ ⟶ R⟦τ⟧.

R⟦x_{1}:τ_{1}, …, x_{n}:τ_{n} ⊢ x_{j}:τ_{j}⟧ =^{def} π_{j}; force
R⟦Γ ⊢ λx.M : σ → τ⟧ =^{def} \overline{λ}_{R⟦σ⟧}((force; thunk) ⊗ R⟦Γ⟧; R⟦x : σ, Γ ⊢ M : τ⟧)
R⟦Γ ⊢ MN : τ⟧ =^{def} ⟨\overline{λ}_{1} R⟦Γ ⊢ N : σ⟧, R⟦Γ ⊢ M : σ → τ⟧⟩; apply

7.2.4 Definition Given a ⊗¬-category, we define the modified Reynolds call-by-value semantics R′⟦−⟧ as follows.

R′⟦σ → τ⟧ =^{def} ¬(¬¬R′⟦σ⟧ ⊗ ¬R′⟦τ⟧)
R′⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ =^{def} R′⟦τ_{1}⟧ ⊗ ⋯ ⊗ R′⟦τ_{n}⟧

A judgement Γ ⊢ M : τ denotes a morphism R′⟦Γ⟧ ⟶ R′⟦τ⟧.

R′⟦x_{1}:τ_{1}, …, x_{n}:τ_{n} ⊢ x_{j}:τ_{j}⟧ =^{def} π_{j}
R′⟦Γ ⊢ λx.M : σ → τ⟧ =^{def} \overline{λ}_{R′}_{⟦σ⟧}(force ⊗ R′⟦Γ⟧; R′⟦x : σ, Γ ⊢ M : τ⟧)
R′⟦Γ ⊢ MN : τ⟧ =^{def} ⟨\overline{λ}_{1} R′⟦Γ ⊢ N : σ⟧, R′⟦Γ ⊢ M : σ → τ⟧⟩; apply

7.3 Uncurrying call-by-name semantics

The uncurrying call-by-name semantics relies on the variant λ-abstraction \underline{\underline{λ}} and application \underline{\underline{apply}}.

7.3.1 Definition (Semantics for uncurrying call-by-name)

N ⟦τ_{1} → … → τ_{n} → b⟧ =^{def} ¬(N ⟦τ_{1}⟧ ⊗ ⋯ ⊗ N ⟦τ_{n}⟧ ⊗ ¬N ⟦b⟧)
N ⟦x_{1} : τ_{1}, …, x_{n} : τ_{n} ⊢ x_{j} : τ⟧ =^{def} π_{j}
N ⟦Γ ⊢ λx.M : σ → τ⟧ =^{def} \underline{\underline{λ}}_{N ⟦σ⟧} N ⟦x : σ, Γ ⊢ M : τ⟧
N ⟦Γ ⊢ MN : τ⟧ =^{def} ⟨N ⟦Γ ⊢ N : σ⟧, N ⟦Γ ⊢ M : σ → τ⟧⟩; \underline{\underline{apply}}

The uncurrying call-by-name semantics, by contrast with the Plotkin one, uses an application map that is itself central (see 4.5.5). Hence all denotations are central. This it what make this semantics validate both the full β and η.

We can now explain more conceptually the invariant (lemma 3.3.1) that made the syntactic soundness proof of the uncurrying call-by-name CPS transform work: it is just one of the many equivalent characterisation of centrality, namely thunkability.

Both the denotations of variables and the application map used by the uncurrying semantics are central (denotations of λ-abstraction are central for all semantics considered here). This is what makes the uncurrying semantics genuinely call-by-name. For the Plotkin call-by-name semantics, by contrast, not even the denotations of variables are central.

7.3.2 Proposition For the special case of the term model, that is K = K(CPS), the denotation of a judgement is the equivalence class of its CPS transform.

V⟦Γ ⊢ M : τ⟧ = [⦇Γ⦈, k : ¬⦇τ⦈ ⊢ ⦇M⦈(k)]
P⟦Γ ⊢ M : τ⟧ = [L⦇Γ⦈, k : ¬⦇τ⦈ ⊢ ⦇M⦈(k)]
R⟦Γ ⊢ M : τ⟧ = [R⦇Γ⦈, k : ¬⦇τ⦈ ⊢ ⦇M⦈(k)]
R′⟦Γ ⊢ M : τ⟧ = [R′⦇Γ⦈, k : ¬⦇τ⦈ ⊢ ⦇M⦈(k)]
N ⟦Γ ⊢ M : τ⟧ = [N ⦇Γ⦈, k : ¬N ⦇τ⦈ ⊢ N ⦇M⦈(k)]

For the shorthand transformation this reads as follows

V⟦Γ ⊢ M : τ⟧ = [\overline{Γ}, k : ¬τ ⊢ \overline{M}]
P⟦Γ ⊢ M : τ⟧ = [\underline{Γ}, k : ¬τ ⊢ \underline{M}]
N ⟦Γ ⊢ M : τ⟧ = [\underline{\underline{Γ}}, k : ¬τ ⊢ \underline{\underline{M}}]

7.4 State and meta-continuation-passing

A very elementary construction on a premomoidal category is to “add state”: each morphism takes and returns an additional argument. This can be regarded as depending on, and possibly modifying, some global variable. Whiel this is not a very satisfactory account of state in programming languages, it is sufficient to give a categorical counterpart of meta-continuation passing style 3.2.4 on page 50. The combination of first-class control and (even a single piece of global) state seems to be a rather powerful one; see [Fil94b].

ObK!S = ObK
K!S(A, B) = K(S ⊗ A, S ⊗ B)
¬^{S}A = ¬(S ⊗ A)
¬^{S}f = S ⊗ ¬f
ϕ_{A}^{S}f = ϕ_{S⊗A}f
thunk^{S} = S ⊗ pair

7.4.1 Remark In K!S, we have operations for dereferencing and assigning to the single piece of state:

:= : S ⟶ 1
! : ! ⟶ S

These are just the second projection

:= =^{def} π_{2} : S ⊗ S ⟶ S = 1 ⊗ S

the diagonal map

! =^{def} δ_{S} : S ⊗ 1 = S ⟶ S ⊗ S

in the base category K. Note that, in the CPS term model ! = [sk ⊢ k⟨ss⟩] and := = [sxk ⊢ k⟨x⟩]. In that sense, ! is essentially the same as callcc, and := the same as throw. There may be a connection with the relationship, mentioned in the introduction, between figures 1.7 and 1.8, and figures 1.5 and 1.6. Compare also figures 6.16 and 6.15 in Chapter 6.

7.4.2 Conjecture We conjecture that, under some “mild” conditions on K, if K is a ⊗¬-category and S ∈ ObK, then K!S is a ⊗¬-category.

Meta-continuation-passing style arises as the special case S = ¬R. Expressions of type R can be aborted and control-delimited.

7.5 Categorical semantics for CPS calculus

Assigning a morphism to each CPS calculus judgement is made somewhat awkward by the fact that these judgements have only premises (typing for names), but no conclusions (type for the whole term), so that there does not seem to be a canonical choice for the codomain of its denotation. This is perhaps surprising, but we would argue that CPS is so low-level that even composition is not fundamental, but a relatively involved idiom from its perspective.

We could either choose a “dummy” codomain (the corresponding continuation never being used) or single out one of the names in a judgement as the current continuation corresponding to the codomain.

Compared to the (syntactic) CPS transforms from the CPS literature, the first of these is analogous to a Continuation Grabbing Style [Sab96] transformation, while the second would amount to a Back to Direct Style [Dan94, DL92] transform.

We sketch each of those, before giving a more detailed account of a Direct Style semantics for a small fragment of CPS calculus.

7.5.1 Continuation Grabbing Style semantics for CPS-calculus

A Continuation Grabbing Style semantics for CPS-calculus could be defined as follows.

⟦Γ ⊢ M⟧ : ⟦Γ⟧ ⟶ ¬1

⟦Γ, x⃗ : σ⃗, k : ¬σ⃗ ⊢ k⟨x⃗⟩⟧
= π_{j}
⟦Γ ⊢ M{n⟨x⃗⟩=N}⟧
= δ_{⟦Γ⟧}; ⟦Γ⟧ ⊗ ⟦Γ⟧ ⊗ thunk_{1}; ⟦Γ⟧ ⊗ (ϕ_{⟦Γ⟧}⟦Γ, n : ¬σ⃗ ⊢ M⟧); ⟦Γ, x⃗ : σ⃗ ⊢ N⟧

7.5.2 Back to Direct Style semantics for CPS-calculus

A Back to Direct Style would have to solve the problem of how to choose a current continuation, corresponding to the codomain of the denotation, in each CPS calculus judgement.

One could introduce conventions for singling out one variable (the last, say) in a judgement as the current continuation. (Incidentally, it is not trivial that this is always possible; we conjecture that in the non-recursive CPS calculus, for each derivable judgement at least one variable in the typing assumptions is assigned a continuation type.)

A judgement Γ, k : ¬τ ⊢ M would then denote a morphism

⟦Γ⟧ ⟶ ⟦τ⟧

The problem is how to define this inductively on the structure of M.

If, in a binding M{n⟨x⃗⟩=N}, k is free in N, but not in M, then the denotation of Γ, k : ¬τ ⊢ M{n⟨x⃗⟩=N} should be a straightforward composition ^{1} of the denotations ⟦Γ, n : ¬σ⃗ ⊢ M⟧ and ⟦Γ, x⃗ : σ⃗ ⊢ N⟧.

But in general k may occur anywhere in M or N or both, requiring a complicated case analysis. (In [Dan94, DL92], this necessitates intricate techniques for digging out a current continuation.)

We restrict ourselves to a fragment of CPS so simple that a few cases are enough: that is we consider only the linear unary CPS calculus. This is a fairly severe restriction: without polyadic continuations, we loose the ability to translate functions. On the other hand this simple fragment has a clean categorical semantics: it is the internal language of a self-adjointness.

It is straightforward to build a category equipped with a self-adjoint functor from the syntax of simply-typed, linear unary CPS calculus along the lines of definition 5.2.1. We focus on the other direction.

1In the co-Kleisli category K_{⟦Γ⟧}

7.5.1 Definition Given a category K together with a self-adjoint functor ¬ : K^{op} ⟶ K with unit force, we define a semantics for the linear unary CPS calculus.

⟦xk ⊢ k⟨x⟩⟧ = id
⟦xk ⊢ x⟨k⟩⟧ = force
⟦xk ⊢ M{n⟨y⟩=N}⟧ = ⟦xn ⊢ M⟧; ⟦yk ⊢ N⟧ , k ∈ FV(N)
⟦xk ⊢ M{n⟨y⟩=N}⟧ = ¬⟦yx ⊢ N⟧; ⟦nk ⊢ M⟧ , k ∈ FV(M)

7.5.2 Lemma ⟦xk ⊢ M⟧ = ⟦yh ⊢ M[xk ↦ yh]⟧

7.5.3 Lemma The denotation of a judgement kx ⊢ M with the variables swapped is the adjoint correspondent of the denotation of the original judgement xk ⊢ M.

¬⟦xk ⊢ M⟧; force = ⟦kx ⊢ M⟧

Proof By induction on M.

k⟨x⟩

¬⟦xk ⊢ k⟨x⟩⟧; force
= ¬id; force
= force
= ⟦xk ⊢ x⟨k⟩⟧

x⟨k⟩

¬⟦xk ⊢ x⟨k⟩⟧; force
= ¬force; force
= id
= ⟦xk ⊢ k⟨x⟩⟧

M{n⟨x⟩=N} First, let k ∈ FV(N).

¬⟦xk ⊢ M{n⟨y⟩=n}⟧; force
= ¬(⟦xn ⊢ M⟧; ⟦yk ⊢ N⟧); force
= ¬⟦yk ⊢ N⟧; ¬⟦xn ⊢ M⟧; force
= ¬⟦yk ⊢ N⟧; ⟦nx ⊢ M⟧
= ⟦kx ⊢ M{n⟨y⟩=N}⟧

Because of the naturality of and the triangular identity for force, we have:

¬(¬⟦xk ⊢ M⟧; force); force
= ¬force; ¬¬⟦xk ⊢ M⟧; force
= ¬force; force; ⟦xk ⊢ M⟧
= ⟦xk ⊢ M⟧
□

7.5.4 Proposition The semantics ⟦ ⟧ of linear unary CPS-calculus in definition 7.5.1 is sound with respect to the axioms of the calculus given in definition 2.4.1.

Proof We show for each axiom M_{1} = M_{2} that ⟦M_{1}⟧ = ⟦M_{2}⟧.

Jmp

⟦n⟨x⟩{n⟨y⟩=N}⟧
= ⟦xn ⊢ n⟨x⟩⟧; ⟦yk ⊢ N⟧
= id; ⟦yk ⊢ N⟧
= ⟦yk ⊢ N⟧
= ⟦xk ⊢ M[y ↦ x]⟧
⟦xk ⊢ n⟨k⟩{n⟨y⟩=N}⟧
= ¬⟦yx ⊢ N⟧; ⟦nk ⊢ n⟨k⟩⟧
= ¬⟦yx ⊢ N⟧; force
= ⟦xy ⊢ N⟧
= ⟦xk ⊢ N[y ↦ k]⟧

Eta

⟦xk ⊢ M{n⟨y⟩=k⟨y⟩}⟧
= ⟦xn ⊢ M⟧; ⟦yk ⊢ k⟨y⟩⟧
= ⟦xn ⊢ M⟧; id
= ⟦xn ⊢ M⟧
= ⟦xk ⊢ M[n ↦ k]⟧

⟦xk ⊢ M{n⟨y⟩=x⟨y⟩}⟧

= ¬⟦yx ⊢ x⟨y⟩⟧; ⟦nk ⊢ M⟧
= id; ⟦nk ⊢ M⟧
= ⟦nk ⊢ M⟧
= ⟦xk ⊢ M[n ↦ x]⟧

Float-L: n ∈ FV(L)

• k ∈ FV(M)

⟦xk ⊢ L{m⟨y⟩=M}{n⟨z⟩=N}⟧

= ¬⟦zx ⊢ N⟧; ⟦nk ⊢ L{m⟨y⟩=M}⟧
= ¬⟦zx ⊢ N⟧; ⟦nm ⊢ L⟧; ⟦yk ⊢ M⟧
= ⟦xm ⊢ L{n⟨z⟩=N}⟧; ⟦yk ⊢ M⟧
= ⟦xk ⊢ L{n⟨z⟩=N}{m⟨y⟩=M}⟧

• k ∈ FV(N)

⟦xk ⊢ L{m⟨y⟩=M}{n⟨z⟩=N}⟧

= ¬⟦yx ⊢ M⟧; ⟦mn ⊢ L⟧; ⟦zk ⊢ N⟧
= ¬⟦yx ⊢ M⟧; ⟦mn ⊢ L{n⟨z⟩=N}⟧
= ⟦xk ⊢ L{n⟨z⟩=N}{m⟨y⟩=M}⟧

Float-R: n ∈ FV(M)

• k ∈ FV(N)

⟦xk ⊢ L{m⟨y⟩=M}{n⟨z⟩=N}⟧

= (⟦xm ⊢ L⟧; ⟦yn ⊢ M⟧; )⟦zk ⊢ N⟧
= ⟦xm ⊢ L⟧; (⟦yn ⊢ M⟧; ⟦yk ⊢ N⟧)
= ⟦xm ⊢ L⟧; ⟦yk ⊢ M{n⟨y⟩=N}⟧
= ⟦xk ⊢ L{m⟨y⟩=M{n⟨z⟩=N}}⟧

• k ∈ FV(L)

⟦xk ⊢ L{m⟨y⟩=M}{n⟨z⟩=N}⟧

= ¬⟦zx ⊢ N⟧; ⟦nk ⊢ L{m⟨y⟩=M}⟧
= ¬⟦zx ⊢ N⟧; ¬⟦yn ⊢ M⟧; ⟦mk ⊢ L⟧

= ¬(⟦yn ⊢ M⟧; ⟦zx ⊢ N⟧); ⟦mk ⊢ L⟧
= ¬⟦yx ⊢ M{n⟨z⟩=N}⟧; ⟦mk ⊢ L⟧
= ⟦xk ⊢ L{m⟨y⟩=M{n⟨z⟩=N}}⟧

□

We have established that an extremely distilled version of CPS, a unary namepassing calculus, is the internal language of a self-adjointness. From the point of view of categorical semantics, an intriguing question would be what, if anything, in the categorical structure makes its internal language a name-passing calculus.

Indexed ¬-categories

In Chapter 4, we have mentioned that we can see the continuation functor as indexed. Here we develop this point of view. Although relevant for continuation semantics, the possibility to use either indexed categories or premonoidal structure for the semantics of environments arises in denotational semantics in general, so we treat it at its natural level of generality.

This chapter presents joint work with John Power [PT97]; in particular, the result (8.3.4 below) on which the connection is built is due to him.

8.1 Environments as indices

Traditionally in denotational semantics, there have been two categorical ways of modelling environments. The first is given by finite products in a Cartesian closed category, as for instance in modelling the simply typed λ-calculus. Over the years, that has gradually been extended. For instance, in order to model partiality, one must generalise from finite product structure to symmetric monoidal structure; and more recently, that has been further generalised to the notion of symmetric premonoidal structure [PR97].

A premonoidal category is essentially a monoidal category except that the tensor need only be a functor in two variables separately, and not necessarily a bifunctor: given maps f : A → A′ and g : B → B′, the evident two maps from A ⊗ B to A′ ⊗ B′ may differ. Such structures arise naturally in the presence of computational effects, where the difference between these two maps is a result of sensitivity to evaluation order. So that is the structure we need in order to model environments in the presence of continuations or other such strong computational effects. A program phrase in environment Γ is modelled by a morphism in the premonoidal category with domain ⟦Γ⟧.

The second approach to modelling environments categorically, also used to model the simply typed λ-calculus, is based on indexed categories with structure, and has been heavily advocated, although not introduced, by Bart Jacobs [Jac92]: the slogan is that contexts, which we call environments, are indices for the categories in which the terms definable in that context are modelled. Here, a program phrase in environment Γ is modelled by an element 1 ⟶ ⟦τ⟧ in a category that implicitly depends on Γ, i.e., by an arrow from 1 to ⟦τ⟧ in the fibre of the indexed category over ⟦Γ⟧. We consider a weak version of indexed category with structure, called a κ-category, implicit in recent work by Masahito Hasegawa [Has95]. In the setting of indexed categories, various binding constructs can be studied. A κ-category has a weak first order notion of binding, given by the assertion that reindexing along projections has a left adjoint. In programming terms, that corresponds to a special form that binds an identifier but is not reifying in the sense that it does not produce a first class function. Hasegawa [Has95] compares it to lambda in early LISP.

The first major result of this chapter is to prove the above two models of environments equivalent. More precisely, we show that every symmetric premonoidal category with a little more of the structure cited above, gives rise to a κ-category, and that this gives a bijection between the classes of symmetric premonoidal categories with such structure and κ-categories. The extra structure we need on a symmetric premonoidal category K is a category with finite products C and an identity on objects strict symmetric premonoidal functor J : C ⟶ K. At first sight, that may seem a somewhat complex structure, but in fact, as made precise in [Pow], it is particularly natural category theoretic structure, more so than that of premonoidal structure alone, as it is algebraic structure.

Related Work

The relationship between symmetric premonoidal categories and κ-categories is related to work by Blute, Cockett, and Seely [RBS]. Implicit in their work is the construction which, to a symmetric premonoidal category with a little added structure, assigns a κ-category. The latter are closely related to their context categories. Identifying precisely which indexed categories thus arise did not appear in their work.

Bart Jacobs’ thesis [Jac91] championed the view of contexts as “indices for the terms and types derivable in that context.” We believe this to be relevant not only to type theory but also to the modelling of environments in computer science, and we use it for that purpose in our third approach to continuation semantics.

Ong [Ong96] also uses a fibration to model environments for his categorical formulation of the λμ-calculus [Par92]. As this calculus is an extension of the callby-name λ-calculus, Ong can assume every fibre to be Cartesian closed. However, for call-by-value programming languages like ML or Scheme, one cannot assume Cartesian closure. (And even if one were to assume call-by-name, the intended meaning of callcc would be less than clear.)

8.2 Premonoidal categories

In this section, we recall the definitions of premonoidal category and strict premonoidal functor, and symmetries for them, as introduced in [PR97] and further studied in [Pow]. We also develop a basic construction on a premonoidal category that we will need later. A premonoidal category is a generalisation of the concept of monoidal category: it is essentially a monoidal category except that the tensor need only be a functor of two variables and not necessarily be bifunctorial, i.e., given maps f : A ⟶ B and f′ : A′ ⟶ B′, the evident two maps from A ⊗ A′ to B ⊗ B′ may differ.

Historically, for instance for the simply typed λ-calculus, environments have been modelled by finite products. More recently, monoidal structure has sometimes been used, for instance when one wants to incorporate an account of partiality [RR88]. In the presence of stronger computational effects, an even weaker notion is required. If the computational effects are strong enough for the order of evaluation of f : A ⟶ B and f′ : A′ ⟶ B′ to be observable, as for instance in the case of continuations, then the monoidal laws cannot be satisfied. The leading example for us of such stronger computational effects are those given by continuations. However, for a simple example of a premonoidal category that may be used for a crude account of state [PR97], consider the following.

8.2.1 Example Given a symmetric monoidal category C together with a specified object S, define the category K to have the same objects as C, with K(A, B) = C(S ⊗ A, S ⊗ B), and with composition in K determined by that of C. For any object A of C, one has functors A ⊗ − : K ⟶ K and − ⊗ A : K ⟶ K, but they do not satisfy the bifunctoriality condition above, hence do not yield a monoidal structure on K. They do yield a premonoidal structure, as we define below.

In order to make precise the notion of a premonoidal category, we need some auxiliary definitions.

8.2.2 Definition A binoidal category is a category K together with, for each object A of K, functors h_{A} : K ⟶ K and k_{A} : K ⟶ K such that for each pair (A, B) of objects of K, h_{A}B = k_{B}A. The joint value is denoted A ⊗ B.

8.2.3 Definition An arrow f : A ⟶ A′ in a binoidal category is central if for every arrow g : B ⟶ B′, the following diagrams commute:

\xymatrix{A ⊗ B \ar[r]^{A⊗g} \ar[d]_{f⊗B} & A ⊗ B′ \ar[d]^{f⊗B′} & B ⊗ A \ar[r]^{g⊗A} \ar[d]_{B⊗f} & B′ ⊗ A \ar[d]^{B′⊗f} \\ A′ ⊗ B \ar[r]^{A′⊗g} & A′ ⊗ B′ & B ⊗ A′ \ar[r]^{g⊗A′} & B′ ⊗ A′}

Moreover, given a binoidal category K, a natural transformation α : g ⟹ h : B ⟶ K is called central if every component of α is central.

8.2.4 Definition A premonoidal category is a binoidal category K together with an object I of K, and central natural isomorphisms a with components (A⊗B)⊗ C ⟶ A ⊗ (B ⊗ C), l with components A ⟶ A ⊗ I, and r with components A ⟶ I ⊗ A, subject to two equations: the pentagon expressing coherence of a, and the triangle expressing coherence of l and r with respect to a.

Now we have the definition of a premonoidal category, it is routine to verify that Example 8.2.1 is an example of one. There is a general construction that yields premonoidal categories too: given a strong monad T on a symmetric monoidal category C, the Kleisli category Kleisli(T) for T is always a premoidal category, with the functor from C to Kleisli(T) preserving premonoidal structure strictly: of course, a monoidal category such as C is trivially a premonoidal category. That construction is fundamental, albeit implicit, in Eugenio Moggi’s work on monads as notions of computation [Mog89], as explained in [PR97].

8.2.5 Definition Given a premonoidal category K, define the centre of K, denoted Z(K), to be the subcategory of K consisting of all the objects of K and the central morphisms.

For an example of the centre of a premonoidal category, consider Example 8.2.1 for the case of C being the category Set of small sets, with symmetric monoidal structure given by finite products. Suppose S has at least two elements. Then the centre of K is precisely Set. In general, given a strong monad on a symmetric monoidal category, the base category C need not be the centre of Kleisli(T), but, modulo a faithfulness condition sometimes called the mono requirement [Mog89, PR97], must be a subcategory of the centre.

The functors h_{A} and k_{A} preserve central maps. So we have 8.2.6 Proposition The centre of a premonoidal category is a monoidal category.

This proposition allows us to prove a coherence result for premonoidal categories, directly generalising the usual coherence result for monoidal categories. Details appear in [PR97].

8.2.7 Definition A symmetry for a premonoidal category is a central natural isomorphism with components c : A⊗B ⟶ B ⊗A, satisfying the two conditions c^{2} = 1 and equality of the evident two maps from (A ⊗ B) ⊗ C to C ⊗ (A ⊗ B). A symmetric premonoidal category is a premonoidal category together with a symmetry.

All of the examples of premonoidal categories we have discussed so far are symmetric, and in fact, symmetric premonoidal categories are those of primary interest to us, and seem to be those of primary interest in denotational semantics in general. For an example of a premonoidal category that is not symmetric, consider, given any category C, the category End_{u}(C) whose objects are functors from C to itself, and for which an arrow from h to k is a C-indexed family of arrows α(A) : h(A) ⟶ k(A) in C, i.e., what would be a natural transformation from h to k but without assuming commutativity of the naturality squares. Then, this category, together with the usual composition of functors, has the structure of a strict premonoidal category, i.e., a premonoidal category in which all the structural isomorphisms are identities, which is certainly not symmetric.

8.2.8 Definition A strict premonoidal functor is a functor that preserves all the structure and sends central maps to central maps.

One may similarly generalise the definition of strict symmetric monoidal functor to strict symmetric premonoidal functor.

In order to compare the various models of environments in the next section, we need to study a construction that, to a premonoidal category, assigns a Cat-valued functor.

8.2.9 Definition A comonoid in a premonoidal category K consists of an object C of K, and central maps δ : C ⟶ C ⊗ C and ν : C ⟶ I making the usual associativity and unit diagrams commute.

It follows from centrality of the two maps in the definition of comonoid that one has the usual coherence for a comonoid, i.e., n-fold associativity is well defined, and comultiple products with counits are also well defined.

8.2.10 Definition A comonoid map from C to D in a premonoidal category K is a central map f : C ⟶ D that commutes with the comultiplications and counits of the comonoids.

Again, it follows from centrality that a comonoid map preserves multiple application of comultiplication and counits. Given a premonoidal category K, comonoids and comonoid maps in K form a category Comon(K) with composition given by that of K. Moreover, any strict premonoidal functor sends a comonoid to a comonoid, so any strict premonoidal functor H : K ⟶ L lifts to a functor Comon(H) : Comon(K) ⟶ (L).

Trivially, any comonoid C in a premonoidal category K yields a comonad on K given by − ⊗ C, and any comonoid map f : C ⟶ D yields a map of comonads from − ⊗ C to − ⊗ D, and hence a functor from Kleisli(− ⊗ D), the Kleisli category of the comonad − ⊗ D, to Kleisli(− ⊗ C), that is the identity on objects. So we have a functor from Comon(C)^{op} to Cat, which we denote by s(K). See [PR97] for this construction and another application of it.

Now, given a category C with finite products, every object A of C has a unique comonoid structure, given by the diagonal and the unique map to the terminal object. So Comon(C) is isomorphic to C.

Thus, given a category C with finite products, a premonoidal category K, and a strict premonoidal functor J : C ⟶ K, we have a functor κ(J) : C^{op} ⟶ Cat given by s(K) composed with the functor induced by J from C ≅ Comon(C) to Comon(K).

8.3 κ-categories

In this section, we introduce κ-categories, and show that the construction at the end of Section 8.2 yields an equivalence between premonoidal categories with added structure as we shall make precise, and κ-categories.

Hasegawa has decomposed the λ-calculus into two calculi, the κ-calculus, and the ζ-calculus [Has95]. This analysis arose from study of Hagino’s categorical programming language. The idea of the κ-calculus, also known as the contextual calculus, is that it has product types on which its abstraction and reduction are constructed, and it can be regarded as a reformulation of the first-order fragment of simply-typed λ-calculus, but does not require the exponent types. We do not explicitly present the κ-calculus here. However, we do describe the notion of κcategory, which is a categorical analogue of the definition of κ-calculus. Further, we compare the notion of κ-category with that of symmetric premonoidal category with a extra structure. That relationship is one of the main theorems of the chapter, which we later extend to relate our two main models of continuations.

Given a small category C, a functor from C^{op} to Cat is called an indexed category, a natural transformation between two indexed categories is called an indexed functor. The notion of indexed natural transformation is definable too, and this gives us a evident notion of adjunction between indexed categories. In concrete terms, it amounts to an ObC-indexed family of adjunctions, such that the units and counits are preserved by reindexing along each f : A ⟶ B. And given an indexed category H : C^{op} ⟶ Cat, we denote by H^{op} : C^{op} ⟶ Cat the indexed functor for which H_{A}^{op} = (H_{A})^{op} with H_{f}^{op} defined by H_{f}.

We will need the definitions of H^{op} and adjunctions between indexed categories in later sections to extend the notion of a functor being self-adjoint on the left, as in the semantics for continuations with premonoidal structure used to model environments in Chapter4 to that of an indexed functor being self-adjoint on the left as in the semantics for continutations using κ-categories to model environments in Section 8.4. But now for our definition of κ-category.

8.3.1 Definition A κ-category consists of a small category C with finite products, together with an indexed category H : C^{op} ⟶ Cat such that

• for each object A of C, ObH_{A} = ObC, and for each arrow f : A ⟶ B in C, the functor H_{f} : H_{B} ⟶ H_{A} is the identity on objects

• for each projection π : B × A ⟶ B in C, the functor H_{π} has a left adjoint L_{B} given on objects by − × A

• (the Beck-Chevalley condition) for every arrow f : B ⟶ B′ in C, the natural transformation from L_{B} ∘ H_{f×id_{A}} to H_{f} ∘ L_{B′} induced by the adjointness is an isomorphism.

[figure: H_{B′×A}; L_{B′}; H_{f×id_{A}}; ⟹; H_{B′}; H_{f}; H_{B×A}; L_{B}; H_{B}]

We shall denote the isomorphism associated with the adjunctions given in the definition by

κ : H_{B×A}(C, C′) ≅ H_{B}(C × A, C′).

A κ-category allows us to model the environments in the presence of continuations or other computational effects. Of course, modelling computational effects involves more structure than that of a κ-category: for continuations, it requires the assignment to each type τ of a type ¬τ that awaits an input of type τ. We shall study such structure in Section 8.4, where we shall define an indexed ¬- category. But here, we restrict out attention to modelling environments, and we shall pursue our leading example, that of continuations, later.

8.3.2 Proposition Given a κ-category H : C^{op} ⟶ Cat, there is an indexed functor inc : s(C) ⟶ H as follows: for each A in C, we have a functor from s(C_{A}) to H_{A}. On objects, it is the identity. To define inc_{1} on arrows, given f : A ⟶ B in C, consider the arrow ι_{B} : 1 ⟶ B in H_{B} corresponding under the adjunction to id_{B} in H_{1}. Applying H_{f} to it gives a map H_{f}(ι_{B}) : 1 ⟶ B in H_{A}, or equivalently, under the adjunction, a map from A to B in H_{1}. Define inc_{1}(f) to be that map.

[figure: 1; A; f; B; 1; A; inc_{1}(f); 1; H_{f} (ι_{B}); 1; ι_{B}; B; _{id_{B}}; B; B; B; B; H_{f}]

This plus naturality determines the rest of the structure.

Proof It is immediate that inc_{1} preserves identities, and one can prove that it preserves composition: this follows by proving that for any map f : A ⟶ B in C and any map g : 1 ⟶ C in H_{B}, the map H_{f}(g) corresponds to the composite in H_{1} of inc_{1}(f) with the adjoint correspondent to g. Moreover, this yields a functor inc_{A} for every A, with naturality as required. □

Using proposition 8.3.2, we can exhibit the relationship between symmetric premonoidal categories with specified extra structure and κ-categories. This forms the basis for the first main result of the chapter, Prop 8.3.4. First, for the construction of a κ-category from a symmetric premonoidal category, we have

8.3.3 Proposition Given a small category C with finite products, a small symmetric premonoidal category K and an identity on objects strict symmetric premonoidal functor J : C ⟶ K, the functor κ(J) : C^{op} ⟶ Cat is a κ-category.

Proof It follows immediately from the construction of κ(J) in Section 8.2 that for each object A of C, we have Obκ(J)_{A} = ObC, and that for each arrow f : A ⟶ B in C, the functor κ(J)_{f} is the identity on objects. Moreover, the existence of the adjoints to each κ(J)_{π} follows directly from the construction and the fact that C is symmetric. The Beck-Chevalley condition also follows directly from the construction. □

Now, for the converse, giving our first main result of the chapter.

8.3.4 Proposition Let C be a small category with finite products. Given a κcategory H : C^{op} ⟶ Cat, there are a symmetric premonoidal category K and an identity on objects strict symmetric premonoidal functor J : C ⟶ K, unique up to isomorphism, for which H is isomorphic to κ(J).

Proof Define K to be H_{1}. For each object A of K, equally A an object of C since ObH_{1} = ObC, define − ⊗ A : K ⟶ K by the composite L ∘ H_{!} where ! : A ⟶ 1 is the unique map in C from A to 1. Note that ! is of the form π, so the left adjoint exists. Moreover, for each map g : C ⟶ C′ in K, we have g ⊗ A : C × A ⟶ C′ × A. The rest of the data and axioms to make K a symmetric premonoidal category arise by routine calculation, using the symmetric monoidal structure of C determined by its finite product structure, and by use of the Beck-Chevalley condition.

Define J : C ⟶ K by inc_{1} as in proposition 8.3.2. It follows from the Beck-Chevalley condition that for a map f : A ⟶ B in C, and for a map g : C ⟶ D in H_{B}, we have that H_{f}(g) is given by the composite of J(id_{C} × f) with the adjoint correspondent of g. The Beck-Chevalley condition further implies that (inc_{1}−)⊗A agrees with inc_{1}(−×A). It follows from functoriality of the H_{f}’s that every map in C is sent into the centre of K. Functoriality plus the Beck-Chevalley condition similarly imply that all the structural maps are preserved. So J is an identity on objects strict symmetric premonoidal functor.

It follows directly from our construction of J that κ(J) is isomorphic to H. Moreover, J : C ⟶ K is fully determined by H since C is fixed, K must be H_{1} up to isomorphism, with premonoidal structure as given, and J must agree on maps with the construction as we have given it. Hence, J is unique up to isomorphism.

□

8.4 Continuation semantics in indexed ¬-categories

In this section, we use the definition of κ-category as a basis, together with selfadjointness, for defining the notion of an indexed ¬-category. We then use that latter definition to give our third continuations semantics. In the final section, we shall prove that it is essentially equivalent to the second, i.e., that given by ⊗¬-categories.

8.4.1 Definition An indexed ¬-category consists of a κ-category H : C^{op} ⟶ Cat together with an indexed functor ¬ : H^{op} ⟶ s(C) such that inc ∘ ¬ is self-adjoint on the left, together with a coretract thunk of force_{1} ∘ inc, where force is the unit of the self-adjunction, such that

• force is dinatural in A with respect to all maps in H_{1} and

• letting (\underline{\underline{force}}_{A})_{B} be the correspondent under the adjunction to L_{A}H_{π}((force_{1})_{B}), we have

¬force_{1} = thunk_{¬}
thunk; ¬¬\underline{\underline{force}} = \underline{\underline{force}}; thunk
thunk_{A×C} = A × thunk_{C}; A × ¬\underline{\underline{force}}; \underline{\underline{force}}
¬κ^{−1}(id_{C×A}) = ¬_{C}(LH_{!}(force_{1})); force_{C}

The left adjoint to reindexing along projections gives rise to a comonad on each fibre, which we will write as ( ) ⊗ A. Furthermore , using inc, we have a diagonal map δ_{A} : A ⟶ A ⊗ A in each fibre.

The thinking behind the definition is as follows. The category C with its finite product structure allows us to model an environment as the product of the types it contains. In the indexed category, program phrases defined in an environment will be modelled as elements in the fibre over the denotation of that environment.

\xymatrix{⟦σ_{1}⟧ × ⋯ × ⟦σ_{n}⟧ \\ 1 \ar[d]^{⟦x_{1}:σ_{1},...,x_{n}:σ_{n}⊢M:τ⟧} \\ ⟦τ⟧}

The isomorphism of adjunction κ is a first order binding construct that allows us to make the dependency of a program phrase on certain variables explicit. The negation functor is much as before, except that it now acts on those variables explicitly singled out by a previous κ.

[figure: Γ × C; Γ; A; f; A × C; κf; B; B; κ; Γ; Γ; A; f; ¬B; B; ¬A; ¬f; ¬]

The motivation for the axioms is as for ⊗¬-categories, except that here, we can avoid one of the axioms as it follows from the indexing of ¬. However, we need our last axiom here in order to make the indexing of ¬ coherent: intuitively, it means that negating the retrieving of a value of type C from the environment to cons a value of type C to a value of type A gives us an operation of partially satisfying demand for a value of type C while leaving the demand for a value of type A untouched.

This formalism, unlike that for a ⊗¬-category, separates the data and the control mechanisms. The indexed functor ¬ is in some sense oblivious to the indexed structure with which first order data manipulation is described. We do not want control to interfere with any data with which it is not concerned. So the ability to model continuations with indexed categories as we do here is a clear indication that we have separated the two. In the final section, we show that this modelling is essentially equivalent to that using premonoidal categories and self-adjointness. We take this as evidence that modelling continuations by self-adjointness is a robust notion in the sense that it is not overly sensitive to the way we model environments, as we could model them in two different ways, in each case fitting the self-adjointness into this framework.

To model λ+callcc, types are interpreted as objects in C. Environments are interpreted using the product in C.

⟦¬τ⟧ =^{def} ¬⟦τ⟧
⟦σ → τ⟧ =^{def} ¬(⟦σ⟧ ⊗ ¬⟦τ⟧)
⟦x_{1} : τ_{1}, …, x_{n} : τ_{n}⟧ =^{def} ⟦τ_{1}⟧ × ⋯ × ⟦τ_{n}⟧

A judgement Γ ⊢ M : τ denotes an element ⟦Γ ⊢ M : τ⟧ : 1 ⟶ ⟦τ⟧ in the fibre over ⟦Γ⟧.

⟦x_{1} : τ_{1}, … x_{n} : τ_{n} ⊢ x_{j} : τ_{j}⟧ =^{def} H_{π_{j}}κ^{−1}(id_{⟦τ_{j}⟧})
⟦Γ ⊢ λx.M : σ → τ⟧ =^{def} thunk; ¬(κ¬⟦Γ, x : σ ⊢ M : τ⟧)
⟦Γ ⊢ throw M N : σ⟧ =^{def} ⟦Γ ⊢ M : ¬τ⟧; ¬(κ(H_{π_{1}}⟦Γ ⊢ N : τ⟧)); force
⟦Γ ⊢ callcc M : τ⟧ =^{def} ⟦Γ ⊢ M : ¬τ → τ⟧; ¬δ; force
⟦Γ ⊢ MN : τ⟧ =^{def} ⟦Γ ⊢ M : σ → τ⟧; ¬(⟦Γ ⊢ N : σ⟧ ⊗ ¬⟦τ⟧); force

Again, the semantics as such is not the topic of the present chapter. We only give some hint at how it is intended to work.

We write a morphism from X to Y in the fibre over C as

\xymatrix{X \ar[r]_{C} & Y}

The most interesting clause is the one for λ-abstraction in that abstracting over a variable implies moving from one fibre to another.

In a more traditional (call-by-name) setting, λ would be interpreted by means of an adjoint to reindexing. Here, it is more elaborate, as it is decomposed into the first-order abstraction given by the structure on the fibration on the one hand and the fibrewise “negation” given by the continuation functor on the other.

A judgement Γ, x : σ ⊢ M : τ denotes a morphism

\xymatrix{1 \ar[r]_{⟦Γ⟧×⟦σ⟧} & ⟦τ⟧}

Negating this given an arrow

\xymatrix{¬⟦τ⟧ \ar[r]_{⟦Γ⟧×⟦σ⟧} & ¬1}

which, by virtue of κ, amounts to

\xymatrix{⟦σ⟧ × ¬⟦τ⟧ \ar[r]_{⟦Γ⟧} & ¬1}

Negating this yields a morphism

\xymatrix{¬¬1 \ar[r]_{⟦Γ⟧} & ¬(⟦σ⟧ × ¬⟦τ⟧)}

All that remains to be done in order to get the meaning of λx.M is to precompose with thunk : 1 ⟶ ¬¬1, taking care of the double negation:

\xymatrix{1 \ar[r]_{⟦Γ⟧} & ¬¬1 \ar[r]_{⟦Γ⟧} & ¬(⟦σ⟧ × ¬⟦τ⟧)}

8.5 Relating ⊗¬-categories and indexed ¬-categories

In this final section of the chapter, we build upon the equivalence between κcategories and symmetric premonoidal categories with the extra structure specified in Proposition 8.3.4 to relate ⊗¬-categories and indexed ¬-categories. They are almost but not quite equivalent. The only difference lies implicit in Proposition 8.3.4: for our definition of ⊗¬-category, we assert that the centre of our category has finite products, whereas Proposition 8.3.4 merely asserts that we have a category with finite products mapping, as the identity on objects, into the centre of our category. We regard this as a minor difference, as the latter merely extends the former mildly without changing any other structure.

Let δ_{A} =^{def} ⟨id_{A}, id_{A}⟩ : A ⟶ A ⊗ A

Let K be a ⊗¬-category. Let ∗_{A} be the Kleisli composition

f ∗_{A} g =^{def} ⟨π_{1}, id⟩; A ⊗ f ⊗ g

Define ¬_{A} : Kleisli_{K}(A ⊗ ( ))^{op} ⟶ Kleisli_{Z(K)}(A × ( )) by ¬_{A}B = B on objects and by

¬_{A}f =^{def} A ⊗ ¬f; \underline{\underline{apply}}_{A}

on morphisms. This is well-defined: A ⊗ ¬f is central, because ¬f is, and

\underline{\underline{apply}} = \underline{\underline{apply}}; thunk; ¬thunk = thunk; ¬¬\underline{\underline{apply}}; ¬thunk

is also central.

Define force_{A} : ¬_{A}¬_{A}B ⟶ B in Kleisli_{K}(A ⊗ ( )) by force_{A} =^{def} π_{2}; apply_{1}. ¬_{A} preserves identities π_{2} : A ⊗ B ⟶ B because

¬_{A}(π_{2})

= ¬_{A}(! ⊗ B)
= A ⊗ ¬(! ⊗ B); A ⊗ ¬(A ⊗ apply_{1}); apply_{A}
= A ⊗ ¬(A ⊗ apply_{1}; ! ⊗ B); apply_{A}
= A ⊗ ¬(! ⊗ ¬¬B; 1 ⊗ apply_{1}); apply_{A}
= A ⊗ ¬(1 ⊗ apply_{1}); A ⊗ ¬(! ⊗ ¬¬B); apply_{A}
= A ⊗ ¬(1 ⊗ apply_{1}); ! ⊗ ¬(1 ⊗ ¬¬B); apply_{1}
= A ⊗ ¬(apply_{1}); ! ⊗ ¬¬¬B; apply_{1}
= ! ⊗ ¬B; 1 ⊗ ¬(apply_{1}); apply_{1}
= ! ⊗ ¬B
= π_{2} : A ⊗ ¬B ⟶ ¬B

¬_{A} preserves composition: let f : A ⊗ B ⟶ C and g : A ⊗ C ⟶ D. Then

¬_{A}(f ∗_{A} g)

= ¬_{A}(δ_{A} ⊗ B; A ⊗ f; g)
= A ⊗ ¬(δ_{A} ⊗ B; A ⊗ f; g); A ⊗ ¬(A ⊗ apply_{1}); apply_{A}
= A ⊗ ¬(A ⊗ apply_{1}; δ_{A} ⊗ B; A ⊗ f; g); apply_{A}
= …
= δ_{A} ⊗ A ⊗ A ⊗ ¬C; A ⊗ A ⊗ ¬g; A ⊗ A ⊗ ¬(A ⊗ f); \underline{\underline{apply}}_{A⊗A}

¬_{A}(f) ∗_{A} ¬_{A}(g)

= δ_{A} ⊗ ¬B; A ⊗ A ⊗ ¬(A ⊗ apply_{1}; f); A ⊗ apply_{A}; A ⊗ ¬(A ⊗ apply_{1}; g); apply_{A}
= …
= δ_{A} ⊗ A ⊗ A ⊗ ¬C; A ⊗ A ⊗ ¬g; A ⊗ A ⊗ ¬(A ⊗ f); A ⊗ \underline{\underline{apply}}_{A}; \underline{\underline{apply}}_{A}

So the required identity follows from the axiom

apply_{A⊗A′} = ⟨π_{2}, π_{1}⟩ ⊗ ¬(A ⊗ A′ ⊗ ¬B); A′ ⊗ \underline{\underline{apply}}_{A}; apply_{A′}

and the facts that ⟨π_{2}, π_{1}⟩ is central and δ; ⟨π_{2}, π_{1}⟩ = δ.

The triangular identity ¬_{A}force_{A} ∗_{A} force_{A} = id holds:

¬_{A}force_{A} ∗_{A} force_{A}

= ¬_{A}(π_{2}; apply_{1}) ∗_{A} force_{A}
= (A ⊗ ¬apply_{1}; ¬_{A}(π_{2})) ∗_{A} force_{A}
= (A ⊗ ¬apply_{1}; π_{2}) ∗_{A} force_{A}
= δ_{A} ⊗ ¬B; A ⊗ A ⊗ ¬apply_{1}; A ⊗ π_{2}; π_{2}; apply_{1}
= A ⊗ ¬apply_{1}; δ_{A} ⊗ ¬¬¬B; A ⊗ π_{2}; π_{2}; apply_{1}
= A ⊗ ¬apply_{1}; π_{2}; apply_{1}
= A ⊗ ¬apply_{1}; ! ⊗ ¬¬¬B; apply_{1}
= ! ⊗ ¬B; 1 ⊗ ¬apply_{1}; apply_{1}
= π_{2}; ¬apply_{1}; apply_{1}
= π_{2} : A ⊗ ¬B ⟶ ¬B

force is natural:

¬_{A}¬_{A}f ∗_{A} force_{A}
= A ⊗ ¬(A ⊗ ¬f; \underline{\underline{apply}}_{A}); \underline{\underline{apply}}_{A}; apply_{1}
= A ⊗ ¬\underline{\underline{apply}}_{A}; A ⊗ ¬(A ⊗ ¬f); apply_{A}
= A ⊗ ¬\underline{\underline{apply}}_{A}; apply_{A}; f
= A ⊗ apply_{1}; f
= force_{A} ∗_{A} f

Putting this all together, it follows that we have

8.5.1 Proposition Given a ⊗¬-category, (K, ¬, apply, thunk), the construction (κ(J), ¬_{A}, force_{A}) as above, together with the given thunk, give an indexed ¬- category.

Proof Most of the proof is given above. For the rest, the axioms hold simply because the category H_{1} is given by K. □

8.5.2 Proposition Given a symmetric premonoidal category K for which the premonoidal structure restricts to finite product structure on the centre, to extend this to the structure of a ⊗¬-category is equivalent to extending the structure of the κ-category κ(J) to that of an indexed ¬-category.

Proof We need to prove that the construction of the proposition is a bijection up to isomorphism. Given an indexed ¬-category, one can obtain a ⊗¬-category by considering H_{1}. In order to show that the construction applied to that ⊗¬- category yields the original indexed ¬-category, everything is routine provided one can show that for any indexed ¬-category, the behaviour of ¬ on H_{1} determines its behaviour on H_{A} for all A. But this follows from the fact that ¬ is indexed and from the final axiom. □

There is little difference between the notions of indexed ¬-category and ⊗¬- category. The only difference between them lies in the choice of an explicitly given category with finite products and an identity on objects strict monoidal functor into a symmetric premonoidal category rather than consideration of a property of the centre. The former is the structure given naturally by an indexed ¬-category. Computationally, it is natural to assume that in the presence of first-class continuations the whole of the centre admits finite products. This is because the self-adjoint structure allows every central morphism to be reified, as explained in section 6.3.

Towards a graphical representation of CPS

In this chapter, we present a graphical notation that may be seen as an extreme distillation of CPS (a negation-only fragment). This graphical representation relates to CPS roughly as Milner’s graphical action structure PIC [Mil93] for the π-calculus relates to the full π-calculus. We may regard it as giving some insight, though the match with CPS is not a perfect one. (See also [Mil94] and [Par95].)

Given the composition and identity definable in the calculus, we naturally arrive at a (“CPS”) monoid. Despite its simplicity it has some of the deeper structure characteristic of CPS: considered as a one-object category, it comes equipped with a contravariant functor self-adjoint on the left and on the right.

Among the aspects of CPS that can be illustrated by the graphical presentation we would like to point out the following:

• The self adjointness, and in particular

• a view of the isomorphism of adjunction as turning a program upside down

• A view of variables as nodes in a graph or pointers

While our graphical formalism allows to visualise the above, it fails to address other aspects of CPS. The self-adjointness “degenerates” (in the sense of a line degenerating to a point, say) to a duality. However, the degeneracy is not a collapse (in the sense that all morphisms are equal).

9.1 A graphical calculus

The [figure: •]-calculus consists of boxes , possibly containing “bullets” •, linked by directed edges [figure].

An arrow can be linked (on either end) to the box or the bullet it contains; hence there are four possible ways (apart from the direction of the arrow) in which two boxes can be linked.

[figure: •; •; •; •]

If nothing connects to the bullet, we omit it.

The calculus has the following four rules:

[figure: =; (α)]
[figure: •; =; (β)]
[figure: •; •] = •  (η)
[figure: =; (σ)]

Strictly speaking, these rewrite rules are only a shorthand for a more complicated pattern matching. In the last two laws, the two boxes are to be fused into one; this (and if there is one, the • inside) inherits all arrows connected to either of the fused boxes. For example, applying η may look like this:

[figure: •; •; •]

9.2 Duality, or inside out

We point out two dualities. Poincaré duality: the rôles of boxes and arrows are interchanged, while the bullet/box distinction remains:

[figure: is dual to; •; is dual to; •]

While the Poincaré duality appears to reflect a certain symmetry of the formal set-up, the second duality, that between boxes and bullets may be more relevant as an operation.

[figure: is dual to; •; •; is dual to]

This latter duality can be regarded as “turning inside out” inasmuch as the dual of putting the left box into the one on the right

[figure: •]

is given by putting the right box into the left one

[figure: •]

The rules are connected by the dualities like this:

[figure: Poincaré; (α); (σ); bullet/ box; (β); (η)]

So up to the two dualities, the four laws are only a single law, stating that connected things at the same level of box nesting can be merged.

9.3 The CPS monoid

The elements of the CPS monoids are finite nonempty sequences of boxes, where any two adjacent ones are connected in one of the eight possible ways, i.e. any box/bullet combination and any direction of the connecting arrow. We usually draw these vertically or, to save space, from left to right. Elements which can be proved equal using the laws are identified (so the elements of the monoid are actually equivalence classes, but this will be glossed over by representative-wise definitions etc.).

We use

[figure]

as a meta-notation ranging over morphisms. Here are some examples of morphisms that will be used later.

[figure: id; •; force; •; thunk; •; •; •]

In addition to these constants, we also have operations on morphisms. For morphisms f and g, the composition f; g, the negation ¬f and the transpose ϕf are defined as follows:

f; g [figure: f; •; g] ¬f [figure: f; •; •] ϕf [figure: f]

The associativity of composition is trivial.

We write proofs of equations about the CPS monoid as sequences of graphs side by side, with adjacent graphs transformed into each other by one rewrite step. id is the identity:

[figure: •]=_{β} [figure]=_{σ} [figure] [figure: •; •]=_{α} [figure: •; •]=_{η} [figure]

9.4 Self-adjointness, or upside down

9.4.1 Proposition ¬ is a contravariant functor right adjoint to its dual. force is both the unit and counit of this adjunction.

Proof

¬ preserves identities ¬id = id:

[figure: •; •; •; •; •; •; •]

¬ preserves composition ¬g; ¬f = ¬(f; g):

[figure: g; •; •; f; •; •; g; •; f; •; •; g; •; f; •; •]

¬ is natural: ¬¬f; force = force; f

[figure: •; •; f; •; •; •; •; •; f; •; •; •; •; f; •; •; •; f; •; •; f]

triangular identity for ¬: ¬force; force = id

[figure: •; •; •; •; •; •; •; •; •; •; •]

□

This completes the proof of the adjointness. Notice that the adjoint correspondent of a morphism f is just its transpose ϕf, that is, f upside down.

The self-adjointness on the right follows from the fact that force and thunk are actually inverses in this model, i.e. force; thunk = id. Hence force is the unit of a duality; but then so is its inverse thunk.

[figure: •; •; •; •; •; •; •; •; •; •; •; •; •; •; •; •; •; •]

9.5 A semantics for linear unary CPS calculus

Recall the typing of linear unary CPS calculus.

\overline{xk ⊢ k⟨x⟩}  \overline{xk ⊢ x⟨k⟩}
\frac{xn ⊢ M yk ⊢ N}{xk ⊢ M\{n⟨y⟩=N\}}  \frac{nk ⊢ M yx ⊢ N}{xk ⊢ M\{n⟨y⟩=N\}}

We can give a semantics for the linear unary CPS-calculus in the [figure: •]- monoid

[figure: ⟦xk ⊢ k⟨x⟩⟧; •; ⟦xk ⊢ x⟨k⟩⟧; •; ⟦xk ⊢ M{n⟨y⟩=N}⟧; ⟦xn⊢M⟧; •; ⟦yk⊢N⟧; ⟦xk ⊢ M{n⟨y⟩=N}⟧; ⟦yx⊢N⟧; •; •; ⟦nk⊢M⟧]

This semantics can be summed up in the following recipe for drawing the graphical representation of a term. To translate a linear unary CPS term, do the following:—

• α-convert if necessary, making all variables pairwise distinct;

• draw a box for each variable;

• for every subterm of the form k⟨x⟩, draw an arrow from the box for x to a bullet in the box for k;

• for every subterm of the form M{n⟨x⟩=N}, draw an arrow from a bullet in the box for N to the box for x.

9.6 Duality and degeneracy

Put crudely, the graphical representation can account for … ⟨…⟩, but not … {… = …}. This is analogous to the way PIC does not account for guarding [Mil94].

The self-adjointness and the duality appear closely connected.

Somewhat more ominously, we have

force; thunk = id

This means that force and thunk are both isomorphisms; hence both self-adjointnesses collapse to a duality.

Thus this calculus is a degenerate model even of the tiny fragment of CPS that it can describe, as it does not adequately account for reification. It is reification (wrapping things into closures) that makes a proper CPS negation non-involutive. On the other hand, the calculus has a great deal of the intuitive flavour of CPS; and if the missing ingredient is indeed only reification, we could hope for the following connection between CPS negation (self-adjointness, unit not iso) and classical negation (duality):

CPS − reification = duality = classical negation

If this is so, one should get a classical negation from CPS by adding a law for dissolving closures:

classical negation = CPS + (−reification)

So the degeneracy of this model may actually be useful for exploring those connections in a simplified setting. Note that attempting to add extra axioms to the CPS calculus in order to enforce “classicality” will quite easily lead to collapse, e.g. if one makes force; thunk = id by adding the following axiom

h⟨k⟩{k⟨a⟩=n⟨f⟩{f⟨p⟩=p⟨a⟩}} = n⟨h⟩ (inv)

This issue of the degeneracy of a self-adjointness (for instance to a duality), is also explored in Masahito Hasegawa’s manuscript [Has97].

Preliminary though they are, the ideas in this chapter may lead to two complementary directions for further work. On the one hand, we could attempt to add extra structure on the graphs to represent reification. A conventional way to do this would be to add boxes to encapsulate certain subgraphs and preclude certain reductions, as in [Mil94] and [Par95].

On the other hand, the duality aspects of CPS may become clearer if addressed in a setting where there actually is a duality functor. Duality seems to have a powerful, though somewhat ambiguous, influence on intuitions about continuations. (For instance, the subtitle of Filinski’s early categorical account of continuations is “an investigation of duality”.)

A related point is the duality on terms from section 3.6, which can be visualized here as the box/bullet duality.

Conclusions and directions for further research

10.1 Conclusions

We have tried to show that continuations are a universal raw material from which low-level as well as high-level programming language constructs can be crafted. The categorical properties of the operations, such as force basically being the unit of adjunction, appear to defy common prejudices against control manipulation as excessively low-level and unprincipled. Like functions, continuations scale up well, but unlike functions, they do not require “purity”. That is to say, the adjunction is not destroyed by the addition of effects in the style done by call-by-value languages like ML or Scheme.

To some extent what we have attempted here is a bridge-building effort between a tradition of semantics, centred around a few institutions in the USA, relying much more on metacircular interpreters for Scheme than on, say, domains and the more mathematically inclined European (predominantly British) tradition. Whether or not the Schemers need anyone to tell them that there is an adjointness about remains to be seen, but we believe that for the European tradition of semantics, it is of particular importance to maintain the link between theoretical fields such as categorical semantics and programming languages.

10.2 Directions for further work

10.2.1 Language design

The self-adjointness seems characteristic of continuations that are first-class as well as statically bound. Neither ML-style exceptions nor a Lisp-style (dynamically bound) catch construct appear to give rise to this kind of structure. We conjecture, then, that self-adjointness is a semantic criterion that sets first-class continuations apart.

Perhaps the best use of category theory in the semantics of programming languages is to rationalise semantic definitions [Ten91]; we hope to add more objective reasons to the general impression that continuations are, for lack of a better word, “cool”. (This may even lend some moral support to the cause of their inclusion in future ML-like languages, whether idealised or real.)

The treatment of continuations in the type system of Standard ML of New Jersey is already fairly conducive to our semantic views of them (not least because it shaped them in the first place). A suggestion that we could offer is to facilitate passing between functions and continuations, by making it easy to convert between, or even identifying arrow types σ → τ with special continuations, i.e. ¬(σ ∗ ¬τ).

In traditional imperative languages, both functions and continuations (in the form of jump labels, say) are very far from being first-class: both can be introduced only as compile-time literals.

But semantically (even more categorically) it is precisely the first-class version that is more natural. First-class functions (of different flavours) give rise to cartesian, or monoidal, or central, closure. First-class continuations give selfadjointness.

On the basis of that, we would argue that callcc is the natural choice of control operator. While advanced, it is not particularly incomprehensible. Attempts to “improve” on it may be counterproductive.

The drive for first-class notions without arbitrary restrictions (as well as the related principle of orthogonality of such notions), appears to be one of the major feedbacks from semantics into practice, in particular language design, and on a more everyday level, programming language teaching.

(Given that first-class control is more natural than plain goto in that it enjoys universal properties, one could be led to speculate whether there is not a notion of first-class state more natural than plain “:=”.)

10.2.2 Applications to programming

While Scheme in particular is expression-oriented, the categorical combinators could form the basis for a more composition-oriented approach to continuations.

We conjecture that they, together with a recursive (reflexive?) continuation type, could be used as a set of primitives for upward (in the sense of [FWH92]) continuations and coroutines in particular. (See also sections 1.1.2 and 1.2) in the introduction.

A related point is that we seem to be almost forced to define a continuation transformer akin to the negation functor for reasoning about callcc in the setting of an ML style typing of continuations. See 7.1.1.

10.2.3 Relation to π- and related calculi

Continuation Passing Style transformations have been studied primarily as a translation between different versions of λ calculus. Such a translation between different λ-calculi is indifferent to the calling mechanism of the target calculus. What appears to have been studied much less (although it underlies CPS compiling) is that CPS is indifferent even as to whether the target language is a λ-calculus at all, in that function application is translated into a kind of message passing between caller and callee. In that sense, CPS transforms are closer in spirit to π-calculus and related formalisms than to λ-calculi.

Much of the expressiveness of the π-calculus appears to be due to the fact that its “first-class” names can be used to implement generalized first-class continuations. We propose to find criteria for when names are used in such a CPS discipline, in order to scale up some of the essentially simple structure of CPS from the sequential to a concurrent scenario. In particular, a categorical characterization of continuation types appears to be quite robust in the sequential case, so that a generalisation to channels used as continuations appears possible. This would allow the isolation of a class of well-behaved computations lying properly between the purely functional and the fully concurrent. The ability of the languages in this class to accommodate (generalized) continuations would provide a more modular account of their expressive power. This would automatically entail the existence of encodings (CPS transforms) of various evaluation strategies for the λ-calculus, with control operators for free.

A related use of CPS as a unifying principle would be to take the existence of a CPS transform between two languages as a comparison of their expressiveness. We conjecture that a CPS discipline can be found not only in λ-calculus encodings [Bou97], but also in Sangiorgi’s translation of Higher Order π-calculus back into the π-calculus.

We hope to explore these connections and extend the analysis of CPS as a particularly structured form of name-passing. Dually, given the popularity of graphical representations in concurrency, aspects of CPS could perhaps be elucidated by building on graphical presentations of, say, the π-calculus. Other techniques from concurrency that could profitably be conferred on CPS include contextual notions of equivalence and bisimulation.

Among the uses of CPS in compiling, we may distinguish between optimisations and translations to more low-level constructs.

The former do not change the character of the language: they re-write λ-terms into other, more efficient, λ-terms. The latter eliminate the λ altogether in favour of jumps with arguments.

In [FSDF93], it is argued that CPS is superfluous for optimisation purposes, as one can optimise while staying in the source language by so-called A-reductions that allow the same optimisations to be made as after a CPS transform. That would imply that CPS transforms, if considered as transforms from the λ-calculus to itself, are of little use to compiling. That would shift the emphasis to the other aspect of CPS, which we have attempted to address here, namely breaking down the λ’s, as it were.

10.2.4 The expressive power of callcc

Section 6.7 consitutes preliminary evidence to the discriminating power of tt callcc. It seems possible that the equivalence induced on λ-terms by the Plotkin CPS transform (M and N are equivalent iff \underline{M} = \underline{N}) is similarly fine-grained to that given by Milner’s π-calculus encoding, which coincides with the Levy-Longo tree equality [San94]. Specifically, the presence of first-class continuations allows to distinguish values from general computations.

10.2.5 Internal languages

Both the concreteness and ease of use of the CPS calculus and the more abstract viewpoint based on the self-adjointness are useful, not least because they complement each other. Their relationship would be clarified if the CPS calculus were, in a precise sense, the internal language of a ⊗¬-category.

Similarly, one could hope for a fuller development of the flattened calculi as the internal languages for premonoidal categories.

10.2.6 Robustness

We have argued informally that the structure that we propose for first-class continuations is not something specific to the small fragment of programming languages that we actually studied and that it would still be present in a more realistic language with state, I/O and other effects. A connection with state has been sketched in section 7.4.

10.2.7 Refinement of the standard model

As a first refinement of the standard model, we propose that some care should be taken in the choice of the result type R. Of course, literally any type in C could be taken as the result type, but it should be clear that the choice of the terminal object yields a trivial interpretation. Other choices, such as a NNO are more defensible and possibly sufficient for PCF-like languages [SF90].

As one of the fundamental properties of the CPS transform is its being polymorphic in the result type, we would argue that this should be reflected in the model.

Realizability models have enough structure to allow for such a polymorphic result type. In the terminology of [FT95], we could take the result type R to be

the “generic predomain” ( ↓_{C_{0}}^{G}) in a slice of the category of assemblies. (While this

is probably not the best account of a “generic” type is seems quite sufficient for our purposes here.) If we wanted to add recursion, we could simply take the lift of this object instead; that would just amount to its fibrewise lift.

It appears that the abort operator A could not be well typed in this setting. No value produced by a program phrase could be polymorphic enough, as it were, to inhabit the result type.

Another conjecture is that in this setting, the control-flow and the domaintheoretic concepts of lifting should coincide, as on grounds of parametricity one would expect, for any base type A, that

R_{⊥}^{R_{⊥}^{A}} ≅ A_{⊥}

(The question arises: over how big a collection of types does R have to vary for this to hold?)

10.2.8 Relation to polymorphism and semantics in general

The difficulties faced when trying to accommodate continuations as morphisms with a domain but without a codomain, as it were, are in some ways similar to those when attempting to account for polymorphic functions as morphisms without fixed domain and codomain. In each case the mathematical framework seems ill-equipped to deal with them, being based on (monomorphic, non-escaping) functions.

There is also a more direct link to polymorphism in continuation semantics. If one does model continuations as functions, at least one should wish to do justice to the parametricity of the answer type and explore its ramifications. Examples of the latter are the existence or non-existence of the abort operator and control delimiters and the issue of full abstraction without them. Felleisen and Sabry show that in their model, control operators are necessary for full abstraction [SF90].

The approach in this thesis could help the search for a better denotational account inasmuch as it points out what to look for, namely self-adjointness, and what not (exponentials). One of the goals of the present approach was to give an account of continuation semantics without answers, so to speak.

Bibliography

[ACS96] Roberto M. Amadio, Ilaria Castellani, and Davide Sangiorgi. On bisimulations for the asynchronous pi-calculus. In Seventh International Conference on Concurrency Theory (CONCUR ’96), volume 1119 of Lecture Notes in Computer Science. Springer Verlag, August 1996.

[App92] Andrew Appel. Compiling with Continuations. Cambridge University Press, 1992.

[Bac78] John Backus. Can programming be liberated from the von Neumann style? Comm. ACM, 8:613–641, 1978.

[BCL^{+}96] Edoardo Biagioni, Ken Cline, Peter Lee, Chris Okasaki, and Chris Stone. Safe-for-space threads in Standard ML. In Proceedings 2nd ACM SIGPLAN Workshop on Continuations, number NS-96-13 in BRICS Notes Series, December 1996.

[Bou97] Gerard Boudol. Pi-calculus in direct style. In ACM Symposium on Principles of Programming Languages, 1997.

[Cro93] Roy Crole. Categories for Types. Cambridge University Press, 1993.

[Dan94] Olivier Danvy. Back to direct style. Science of Computer Programming, 22(3):183–195, 1994.

[DF92] Olivier Danvy and Andrzej Filinski. Representing control, a study of the CPS transformation. Mathematical Structures in Computer Science, 2(4):361–391, December 1992.

[DH94] Olivier Danvy and John Hatcliff. A generic account of continuationpassing styles. In ACM Symposium on Principles of Programming Languages, pages 458–471, 1994.

[DHM91] Bruce Duba, Robert Harper, and David MacQueen. Typing first-class continuations in ML. In Proc. ACM Symp. Principles of Programming Languages, pages 163–173, January 1991.

[Dij68] E. W. Dijkstra. Goto statement considered harmful. Communications of the ACM, 11, 1968.

[DL92] Olivier Danvy and Julia L. Lawall. Back to direct style II: First-class continuations. In ACM Conference on Lisp and Functional Programming, pages 299–310, 1992.

[Fel91] Matthias Felleisen. On the expressive power of programming languages. In Science of Computer Programming, volume 17, pages 35–75, 1991. Preliminary version in: Proc. European Symposium on Programming, Lecture Notes in Computer Science, 432. Springer-Verlag (1990), 134– 151.

[FF96] Matthias Felleisen and Daniel P. Friedman. The Seasoned Schemer. MIT Press, 1996.

[FFKD86] Matthias Felleisen, Daniel P. Friedman, Eugene E. Kohlbecker, and Bruce Duba. Reasoning with continuations. In Proceedings of the Symposium on Logic in Computer Science, pages 131–141, Washington DC, June 1986. IEEE Computer Society Press.

[FH88] Anthony Field and Peter Harrison. Functional Programming. Addison-Wesley, 1988.

[Fil89] Andrzej Filinski. Declarative continuations: an investigation of duality in programming language semantics. In D. H. Pitt et al., editors, Category Theory and Computer Science, number 389 in Lecture Notes in Computer Science, pages 224–249. Springer-Verlag, 1989.

[Fil92] Andrzej Filinski. Linear continuations. In Proceedings of the Nineteenth Annual ACM Symposium on Principles of Programming Languages, 1992.

[Fil94a] Andrzej Filinski. Recursion from iteration. Lisp and Symbolic Computation, 7(1), Jan 1994.

[Fil94b] Andrzej Filinski. Representing monads. In Proceedings of the 21st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, January 1994.

[Fil96] Andrzej Filinski. Controlling Effects. PhD thesis, School of Computer Science, Carnegie Mellon University, 1996.

[Fis72] Michael J. Fischer. Lambda-calculus schemata. In Proceedings ACM Conference on Proving Assertions about Programs, pages 104–109, Los Cruces, 1972. SIGPLAN Notices, 7(1), January 1972.

[Fis93] Michael J. Fischer. Lambda-calculus schemata. Lisp and Symbolic Computation, 6(3/4):259–288, November 1993.

[FSDF93] Cormac Flanagan, Amr Sabry, Bruce F. Duba, and Matthias Felleisen. The essence of compiling with continuations. In Proc. SIGPLAN ’93 Conference on Programming Language Design and Implementation, pages 237–247, June 1993.

[FT95] Michael Fourman and Hayo Thielecke. A proposed categorical semantics for ML modules. In David Pitt et al., editor, Category Theory in Computer Science, number 953 in LNCS. Springer Verlag, 1995.

[FWH92] Daniel P. Friedman, Mitchell Wand, and Christopher T. Haynes. Essentials of Programming Languages. MIT Press and McGraw-Hill, 1992.

[Gri90] Timothy G. Griffin. A formulae-as-types notion of control. In Conference record of the 17th ACM Symposium on Principles of Programming Languages (POPL), pages 47–58, San Francisco, CA USA, 1990.

[Has95] Masahito Hasegawa. Decomposing typed lambda calculus into a couple of categorical programming languages. In David Pitt et al., editor, Category Theory in Computer Science, number 953 in LNCS. Springer Verlag, 1995.

[Has97] Masahito Hasegawa. Continuation monoids. unpublished manuscript, March 1997.

[HD95] John Hatcliff and Olivier Danvy. Thunks and the lambda-calculus. Technical Report Technical report 95/3, DIKU, Computer Science Department, University of Copenhagen, February, 1995.

[HDM93] Robert Harper, Bruce Duba, and David MacQueen. Typing first-class continuations in ML. Journal of Functional Programming, 3(4), October 1993.

[Hen87] Martin C. Henson. Elements of Functional Languages. Blackwell Scientific Publications, Oxford, 1987.

[HFW86] Christopher T. Haynes, Daniel P. Friedman, and Mitchell Wand. Obtaining coroutines with continuations. Journal of Computer Languages, 11(3/4):143–153, 1986.

[Hof94] Martin Hofmann. Sound and complete axiomatizations of call-by-value control operators. Math. Struct. in Comp. Science, 1994.

[HS97] Martin Hofmann and Thomas Streicher. Continuation models are universal for lambda-mu-calculus. In Proc. LICS ’97, 1997. (to appear).

[Ing61] P. Z. Ingerman. Thunks: a way of compiling procedure statements with some comments on procedure declarations. Comm. A.C.M., 4(1):55–58, January 1961.

[Jac91] Bart Jacobs. Categorical Type Theory. PhD thesis, University of Nijmegen, 1991.

[Jac92] Bart Jacobs. Simply typed and untyped lambda calculus revisited. In M.P. Fourman, P.T. Johnstone, and A.M. Pitts, editors, Applications of Categories in Computer Science. Cambridge Univ. Press, 1992.

[KCF92] Ramma Kanneganti, Robert Cartwright, and Matthias Felleisen. SPCF: its model, calculus, and computational power. In Proc. REX Workshop on Semantics and Concurrency, LNCS. Springer-Verlag, 1992.

[Lai97] James Laird. Full abstraction for functional languages with control. In Proc. LICS ’97, 1997. (to appear).

[LD93] Julia L. Lawall and Olivier Danvy. Separating stages in the continuationpassing style transformation. In ACM Symposium on Principles of Programming Languages, pages 124–136, 1993.

[LS86] J. Lambek and P. J. Scott. Introduction to higher-order categorical logic. Cambridge University Press, 1986.

[Mac71] Saunders Mac Lane. Categories for the Working Mathematician. Springer Verlag, 1971.

[Mil91] Robin Milner. The polyadic π-calculus: A tutorial. LFCS Report ECS-LFCS-91-180, LFCS, University of Edinburgh, October 1991.

[Mil93] Robin Milner. Action structures for the π-calculus. Technical Report ECS–LFCS–93–264, LFCS, May 1993.

[Mil94] Robin Milner. Pi-nets: a graphical form of pi-calculus. In European Symposium on Programming, volume LNCS 788, pages 26–42. Springer-Verlag, 1994.

[Mog89] Eugenio Moggi. Computational lambda-calculus and monads. In 4th LICS conference. IEEE, 1989.

[MTH90] Robin Milner, Mads Tofte, and Robert Harper. The Defintion of Standard ML. MIT Press, 1990.

[Mur91] Chet Murthy. An evaluation semantics for classical proofs. In Proc. 11th IEEE Annual Symposium on Logic in Computer Science. IEEE Computer Society Press, 1991.

[Mur92] Chet Murthy. A computational analysis of girard’s translation and LC. In IEEE Annual Symposium on Logic in Computer Science. IEEE Computer Society Press, 1992.

[MW85] Albert R. Meyer and Mitchell Wand. Continuation semantics in typed lambda-calculi (summary). In Rohit Parikh, editor, Logics of Programs, number 193 in Lecture Notes in Computer Science, pages 219–224. Springer-Verlag, 1985.

[NJ93] AT&T Bell Laboratories. Standard ML of New Jersey — Base Environment, 0.93 edition, February 1993.

[Ong96] C.-H. L. Ong. A semantics view of classical proofs: type-theoretic, categorical, denotational characterizations. In Proc. 11th IEEE Annual Symposium on Logic in Computer Science, pages 230–241. IEEE Computer Society Press, 1996.

[Par92] Michel Parigot. λμ-calculus: an algorithmic interpretation of classical natural deduction. In Proceedings International Conference on Logic Programming and Automated Deduction, number 624 in LNCS, pages 190–201, 1992.

[Par95] Joachim Parrow. Interaction diagrams. Nordic Journal of Computing, 2:407–443, 1995.

[Pau91] Lawrence C. Paulson. ML for the Working Programmer. Cambridge University Press, 1991.

[Plo75] Gordon Plotkin. Call-by-name, call-by-value, and the λ-calculus. Theoretical Computer Science, 1(2):125–159, 1975.

[Pow] John Power. Premonoidal categories as categories with algebraic structure. (submitted).

[PR97] John Power and Edmund Robinson. Premonoidal categories and notions of computation. Mathematical Structures in Computer Science, 1997. to appear.

[PS93] Benjamin Pierce and Davide Sangiorgi. Typing and subtyping for mobile processes. In Logic in Computer Science, 1993. Full version in Mathematical Structures in Computer Science, 1996.

[PS96] Benjamin Pierce and Davide Sangiorgi. Typing and subtyping for mobile processes. Mathematical Structures in Computer Science, 6(5):409–454, 1996.

[PT97] John Power and Hayo Thielecke. Environments, continuation semantics and indexed categories. In Proceedings TACS’97, 1997.

[RBS] J.R.B. Cockett R.F. Blute and R.A.G. Seely. Categories for computation in context and unified logic. (submitted).

[Re91] Jonathan Rees and William Clinger (editors). Revised^{4} report on the algorithmic language scheme. ACM Lisp Pointers IV, July-September 1991.

[Ree92] Jonathan Rees. The scheme of things: The june 1992 meeting. ACM Lisp Pointers, 5(4):40–45, 1992.

[Rey93] John C. Reynolds. The discoveries of continuations. Lisp and Symbolic Computation, 6(3/4):233–247, November 1993.

[RR88] Edmund Robinson and Guiseppe Rosolini. Categories of partial map. Information and Computation, 79(2):95–130, 1988.

[RS94] Niels Jakob Rehof and Morten Heine Sørensen. The λ_{Δ} calculus. In M. Hagiya and J. Mitchell, editors, Theoretical Aspects of Computer Software, volume 789 of Lecture Notes in Computer Science, pages 516– 542. Springer-Verlag, 1994.

[Sab96] Amr Sabry. Note on axiomatizing the semantics of control operators. Technical Report CIS-TR-96-03, University of Oregon, 1996.

[San94] Davide Sangiorgi. The lazy lambda calculus in a concurrency scenario. Information and Computation, 111(1):120–153, may 1994.

[San95] Davide Sangiorgi. Lazy functions and mobile processes. Technical Report RR-2515, INRIA-Sophia Antipolis, 1995.

[San97] Davide Sangiorgi. The name discipline of uniform receptiveness. In Proc. ICALP’97, 1997.

[Sch86] David A. Schmidt. Denotational Semantics - A Methodology for Language Development. Allyn and Bacon, 1986.

[SF89] George Springer and Daniel P. Friedman. Scheme and the Art of Programming. MIT Press, 1989.

[SF90] Dorai Sitaram and Matthias Felleisen. Reasoning with continuations II: full abstraction for models of control. In M. Wand, editor, Lisp and Functional Programming. ACM, 1990.

[Shi96] Olin Shivers. Continuations and threads: Expressing machine concurrency directly in advanced languages. In Proceedings 2nd ACM SIG-PLAN Workshop on Continuations, number NS-96-13 in BRICS Notes Series, December 1996.

[SS76] Guy Steele and Gerald Sussman. Lambda: The ultimate imperative. Technical Report AI Memo 353, MIT, March 1976.

[Ste76] Guy Steele. LAMBDA: The ultimate declarative. Technical Report AI Memo 379, MIT, November 1976.

[Ste77] Guy Steele. Debunking the “expensive procedure call” myth or, procedure call implementations considered harmful or, lambda: The ultimate goto. Report AI Memo 443, MIT, 1977.

[Ste78] Guy Steele. Rabbit: A compiler for Scheme. Technical Report AI TR 474, MIT, May 1978.

[SW74] Christopher Strachey and C. P. Wadsworth. Continuations: A mathematical semantics for handling full jumps. Technical Monograph PRG-11, Oxford University Computing Laboratory, January 1974.

[SW96] Amr Sabry and Philip Wadler. A reflection on call-by-value. ACM SIG-PLAN Notices, 31(6):13–24, June 1996.

[Ten91] Robert D. Tennent. Semantics of Programming Languages. Prentice-Hall International, 1991.

[Thi96a] Hayo Thielecke. Continuation passing style and self-adjointness. In Proceedings 2nd ACM SIGPLAN Workshop on Continuations, number NS-96-13 in BRICS Notes Series, December 1996.

[Thi96b] Hayo Thielecke. Continuation semantics, self-adjointness and the πcalculus. Unpublished draft, March 1996.

[Thi97] Hayo Thielecke. Continuation semantics and self-adjointness. In Proceedings MFPS XIII, Electronic Notes in Theoretical Computer Science. Elsevier, 1997.

[Tur95] David Turner. The Polymorphic Pi-Calculus: Theory and Implementation. PhD thesis, University of Edinburgh, 1995.