Call-By-Push-Value: 1.1. Calculi For Functional Programming: Pure and Effectful
Call-By-Push-Value: 1.1. Calculi For Functional Programming: Pure and Effectful
Call-By-Push-Value: 1.1. Calculi For Functional Programming: Pure and Effectful
1. INTRODUCTION
1.1. Calculi for functional programming: pure and effectful
Programming language semantics is a field where we study simple programming lan-
guages or calculi, each of which can have various forms of mathematical descrip-
tion. Firstly, the syntax provides ways of constructing expressions—types and terms—
and a program is a special kind of term. An operational semantics prescribes how to
evaluate programs. By contrast, a denotational semantics (or model) provides every
expression—type or term—with a “denotation” or meaning. It does this composition-
ally, by interpreting each way of constructing expressions as an operation on denota-
tions. Lastly, an axiomatic theory, such as an equational theory, provides a formal way
of reasoning about expressions.
Prominent among the many calculi of interest is the simply typed λ-calculus. It can
be seen as a purely functional programming language: when a program is evaluated,
all that happens is that the result is returned.
However, many programs of interest are not purely functional, as they involve com-
putational effects. An effect can be defined as an “imperative feature” added to a func-
tional program, or perhaps more accurately as an “instruction to the interpreter”.
For example: raising an error; printing; interactive input; making nondeterministic or
probabilistic choices; running forever (via iteration or recursion); reading and writing
to memory; generating new memory cells; saving and restoring the execution stack.
CBPV is a variant of the simply typed λ-calculus that incorporates computational
effects, and therefore synthesizes functional and imperative programming. It was for-
mulated and developed in my book and related papers [Levy 2004; 2001; 1999; 2006a;
2005], building on a large amount of earlier research, especially [Moggi 1991; Filinski
1996].
The fundamental CBPV principle is the distinction between values and computa-
tions, with functions classified as computations. The distinction is not as severe as it
might seem, because to each computation there corresponds a value, called its thunk,
which can be forced (i.e. executed) when desired.
A special feature of CBPV is that it subsumes both the call-by-value (CBV) and call-
by-name (CBN) versions of the simply typed λ-calculus with effects. By “subsume”
I mean not only that CBV and CBN can be translated into CBPV, but that these
transforms preserve every known form of semantics. That includes operational and ab-
stract machine semantics, and also denotational models using domain theory, possible
1 Strictly
speaking, the preservation of operational semantics is up to administrative reductions, and that of
denotational semantics is up to isomorphism, but these are small matters.
2 In CBV, although in principle thunks can be used to express lazy lists, it is rather challenging to correctly
write the concatenation operator (for example), or even its type. See also [MacQueen et al. 1998].
[[x]] : ρ 7→ ρ(x)
[[λx. M ]] : ρ 7→ λa ∈ [[A]]. [[M ]](ρ, x 7→ a)
[[M N ]] : ρ → 7 ([[M ]]ρ)([[N ]]ρ)
and so forth. Strictly speaking, we must prove semantic coherence, the property that
any two derivations of a judgement Γ ` M : B lead to the same denotation. Alterna-
tively, sufficient type annotations can be put into the syntax, so that derivations are
unique. Henceforth we ignore the issue.
2.5. Weakening and substitution
If Γ ⊆ Γ0 , i.e. each declaration in Γ appears also in Γ0 , then the judgement Γ ` M : A
implies Γ0 ` M : A. The weakening lemma obtains [[Γ0 ` M : A]] from [[Γ ` M : A]] by
environment restriction.
Terms that are α-equivalent, i.e. differ only by renaming of bound variables, can be
shown to have the same denotation. Henceforth we identify such terms.
For typing contexts Γ and Γ0 , a substitution k : Γ → Γ0 sends each variable (x : A) ∈ Γ
to (k(x) : A) ∈ Γ0 , and it denotes a function [[k]] : [[Γ0 ]] → [[Γ]] sending ρ to (ρ(k(x))(x:A)∈Γ .
Any term Γ ` M : B gives a substituted term Γ0 ` M [k] : A, which is M with each
Γ `c M : B Γ `v V : U B
Γ `v thunk M : U B Γ `c force V : B
Γ `v V : A Γ `c M : F A Γ, x : A `c N : B
Γ `c return V : F A Γ `c M to x. N : B
Γ `v V : Aı̂ Γ `v V : i∈I Ai Γ, x : Ai `c Mi : B
P
ı̂ ∈ I
Γ `v inı̂ V : i∈I Ai Γ `c match V as (ini x. Mi )i∈I : B
P
Γ `v V : 1 Γ `c M : B
Γ `v hi : 1 Γ `v match V as hi. M : B
Γ `v V : A Γ `v V 0 : A0 Γ `v V : A × A0 Γ, x : A, y : A0 `c M : C
Γ `v hV, V 0 i : A × A0 Γ `c match V as hx, yi. M : C
(Γ `c Mi : B i )i∈I Γ `c M : i∈I B i
Q
ı̂ ∈ I
Γ `c λ(i. Mi )i∈I : i∈I B i Γ `c Mı̂ : B ı̂
Q
Γ, x : A `c M : B Γ `c M : A → B Γ `v V : A
Γ `c λx. M : A → B Γ `c M V : B
3.3. Stacks
There are various kinds of operational semantics. So far we have seen two of them:
interpreter and big-step semantics. Another is the CK-machine [Felleisen and Fried-
man 1986], where C stands for “Computation” and K for “stacK”. The idea is that a
computation is evaluated using a stack, which is initially empty. When we evaluate N ,
and are instructed (by the interpreter) to first evaluate one part of N , the rest is placed
on the stack until this evaluation phase is complete.
β-laws
−−−−→ −−→
let x be V . M = M [V /x]
(return V ) to x. M = M [V /x]
force thunk M = M
match inı̂ V as (ini x. Mi )i∈I = Mı̂ [V /x]
match h i as hi. M = M
match hV, V 0 i as hx, yi. M = M [V /x, V 0 /y]
λ(i. Mi )i∈I ı̂ = Mı̂
(λx.M )V = M [V /x]
η-laws
V = thunk force V
M [V /z] = match V as (ini x. M [ini x/z])i∈I
M [V /z] = match V as h i.M [h i/z]
M [V /z] = match V as hx, yi.M [hx, yi/z]
M = λ(i. M i)i∈I
M = λx.(M x)
Sequencing laws
M = M to x. return x
(P to x. M ) to y. N = P to x. (M to y. N )
(P to x. M )ı̂ = P to x. (Mı̂)
(P to x. M )V = P to x. (M V )
The judgement Γ `k K : B =⇒ C says that, once values are provided for the identi-
fiers in Γ, the stack K may accompany a computation of type B. The top-level type C
is the type of the computation initially loaded onto the machine. (Some readers may
prefer to think of K as an “evaluation context” of type C, with a hole of type B.) The
typing rules are shown in Figure 4.
The CK-machine itself is shown in Figure 5. A configuration M, K : C consists of a
computation type B (which we omit), a computation `c M : B and a stack `k K : B =⇒
Γ `k K : B ı̂ =⇒ C Γ `v V : A Γ `k K : B =⇒ C
ı̂ ∈ I
Γ `k ı̂ :: K : i∈I B i =⇒ C Γ `k V :: K : A → B =⇒ C
Q
C. We write for the transition relation between configurations. It is easily seen that,
if M, K is not terminal, then M, K N, L for a unique configuration N, L : C.
So far, we have used operator-first notation for application. But we can also use
operand-first, writing V ‘M instead of M V , and ı̂‘M instead of Mı̂. Using this notation,
λx can be read as “pop x”, and V ‘ can be read as “push V ”. Likewise, λi can be read as
“pop i” and ı̂‘ can be read as “push ı̂”. This is illustrated by following program, which
uses printing and arithmetic.
print "hello0".
let 3 be x.
let thunk (
print "hello1".
λz.
print "we just popped "z.
return x + z
) be y.
print "hello2".
hello0
hello2
hello3
we just pushed 7
hello1
we just popped 7
w is bound to 10
and finally return the value 15. We now can understand the CBPV slogan: “A value is,
a computation does.” Here is a summary of the types, from this perspective.
There are three important operations on stacks [Levy 2005]. Firstly, the dismantling
of a Γ `k K : B =⇒ C onto a computation Γ `c M : B is written Γ `c M • K : C. It is
defined by induction on K as follows.
def
M • nil = M
def
M • (to x. N :: K) = (M to x. N ) • K
def
M • (ı̂ :: K) = (Mı̂) • K
def
M • (V :: K) = (M V ) • K
4.2. Nondeterminism
We add to the language the following typing rule
(Γ `c Mi : B)i∈I
Γ `c choose (Mi )i∈I : B
According to preference, we can require I to be {left, right}, or to be N, or allow it to be
any set (in which case the terms form a proper class).
To evaluate choose (Mi )i∈I : nondeterministically choose some i ∈ I, then evaluate
Mi . The ⇓ relation and CK-machine transltion are extended accordingly. Allowing I to
be empty requires us to say that evaluation may die at any time.
Γ `c error e : B
for each e ∈ E. To evaluate error e: halt and print the error message e. We extend the
big-step semantics with a relation M e, saying that evaluation of M results in the
error e, in the evident way.
4.4. Printing
Let A be a set of letters, for example {a, b, c, d, e} . We extend CBPV with the typing
rule
Γ `c M : B
Γ `c print c. M : B
for each c ∈ A. To evaluate print c. M : firstly print c and then evaluate M . We can
present the operational semantics for this language in various ways.
(Γ `c Mk : B)k∈Kr
Γ ` inr (Mk )k∈Kr : B
for each r ∈ R. To evaluate inr (Mk )k∈Kr : firstly print r and pause, and if the user then
inputs k ∈ Kr , evaluate Mk . The medium-step and CK-machine semantics are defined
as in Section 4.4. The big-step semantics takes the form M ⇓ t, where M : B is a closed
computation and t is a well-founded s-tree on the set of terminals of type B.
Note that interactive input generalizes both errors and printing, as the set E of
errors gives the signature (∅)e∈E , and the set A of letters gives the signature (1)c∈A .
Γ `v V : bool Γ, x : loc `c M : B
Γ `c new x := V ; M
Γ `v V : loc Γ `v V 0 : loc Γ `c M : B Γ `c M 0 : B
Γ `c if V = V 0 then M else M 0 : B
A world is a natural number, indicating how many cells exist at a given time. The poset
of worlds is written W. (Here W is N, since we allow just one sort of cell.) The empty
world is 0. We have judgements w|Γ `v V : A and w|Γ `c M : B and w|Γ `k K : B =⇒ C
for terms at world w—the case w = 0 giving the ordinary judgements—and we have
the additional rule
06i<w
w|Γ `v li : loc
For a world w, a state at w is a list of w booleans. The big-step semantics takes the form
w, s, M ⇓ w0 , s0 , T , where
— w is a world
— s is a state at w
— M : B is a closed computation at w
— w0 is a world such that w0 > w
— s0 is a state at w0
— T : B is a terminal at w0 .
A WSC-configuration w, s, M : B consists of a world w, and a state s and closed compu-
tation M : B at w. Likewise a WSCK-configuration w, s, M, K : C consists of a world w,
and a state s and closed computation M : B and closed stack K : B =⇒ C at w. For a
world w, a WSC-configuration x, s, M : B or WSCK-configuration x, s, M, K : C is said
to be at w when w 6 x.
4.7. Global store
The global store extension of CBPV is formulated the same way, except that we fix the
world w and disallow new. The big-step semantics takes the form s, M ⇓ s0 , T , and we
simply speak of an SC-configuration s, M : B and an SCK-configuration s, M, K : C.
4.8. Control effects
We extend CBPV with an instruction letstk α meaning “let α be the current stack”,
and an instruction changestk α meaning “change the current stack to α”. We use
α, β, . . . for stack variables, which are distinct from ordinary variables.
A stack variable declaration α : B says (informally) that α is a stack from B to some
unspecified type. A stack context ∆ is a list of these, with no stack variable declared
more than once. The value judgement takes the form Γ `v V : A | ∆ and the computa-
tion judgement Γ `c M : B | ∆, with the following extra rules.
Γ `c M : B | ∆, α : B (α : B) ∈ ∆ Γ `c M : B | ∆
Γ `c letstk α. M | ∆ Γ `c changestk α. M : B 0 | ∆
Γ `c M : B | ∆, α : B Γ `k K : B =⇒ C | ∆ Γ `c M : B [C] ∆
Γ `c letstk α. M | ∆ Γ `c changestk K. M : B 0 [C] ∆
Clearly Γ `c M : B | ∆ implies Γ `c M : B [C] ∆, and likewise for values.
Say that a CK-configuration M, K : C consists of a closed execution computation
M : B [C] and a closed stack K : B =⇒ C. So any closed computation M : C gives a
CK-configuration M, nil : C. The extra transition rules are as follows.
letstk α. M K M [K/α] K
changestk K. M L M K
5. DENOTATIONAL SEMANTICS
Having presented many effectful extensions of CBPV, let us give (in outline) a denota-
tional semantics for each of them. These models satisfy the equational laws, including
the pure continuation law.
5.1. Recursion
For CBPV extended with recursion, the observational preorder relates two terms Γ `
M, N : A when any program that returns n continues to do so when M is replaced by
N . This motivates the use of posets for denotational semantics. W
A cpo A is a poset in which every ω-chain a0 6 a1 6 · · · has a supremum n∈N an .
A function between cpos A → B is continuous when it preserves these suprema (and
hence is monotone). The set of continuous functions from A to B, ordered pointwise,
is written A → B. A cppo is a cpo with bottom element, written ⊥. Any cpo A can be
“lifted” to form a cppo
def
A⊥ = {up(a) | a ∈ A} ∪ {⊥}
ordered as A with an additional bottom element.
Now for the denotational semantics. A value type A denotes a cpo [[A]], thought of
as a semantic domain for values of type A. A computation type B denotes a cppo [[B]],
thought of as a semantic domain for computations of type B. Specifically:
def
[[U B]] = [[B]]
def
[[F A]] = [[A]]⊥
def
[[A → B]] = [[A]] → [[B]]
and so forth. Recursive types—of both kinds—are interpreted using a recipe that was
given in [Smyth and Plotkin 1982]; this leads, for example, to the type rec X. bool × X
denoting the empty cpo. Q
A typing context Γ denotes the cpo (x:A)∈Γ [[A]], whose elements are semantic envi-
ronments. Then:
— A value Γ `v V : A denotes a continuous function [[Γ]] → [[A]].
— A computation Γ `c M : B denotes a continuous function [[Γ]] → [[B]].
All the CBPV equational laws are satisfied, and the pure continuation law follows from
strictness.
A key result is the agreement of the denotational and operational semantics, which
is called “computational adequacy”. Writing ε for the empty environment, we have
that M ⇓ T implies [[M ]]ε = [[T ]]ε, and that M ⇑ implies [[M ]]ε = ⊥. Furthermore, if
we say that a CK-configuration M, K : C denotes the element [[K]](ε, [[M ]]ε) ∈ [[C]], then
M, K N, L implies [[M, K]] = [[N, L]], and M, K ω implies [[M, K]] = ⊥.
5.3. Nondeterminism
Let us give in outline a denotational semantics for CBPV extended with nondetermin-
ism. A value type A denotes a set, thought of a semantic domain for values V : A.
A computation type B denotes a set, thought of a semantic domain for possible be-
haviours of computations M : B. Specifically:
def
[[F A]] = [[A]] since a computation : F A may return a value : A.
def
[[A → B]] = [[A]] × [[B]] since a computation : A → B may pop an value : A
and then behave as a computation : B.
Q def P Q
[[ i∈I B i ]] = i∈I [[B i ]] since a computation : i∈I may pop i ∈ I
and then behave as a computation : B i .
def
[[U B]] = P[[B]] since a value : U B, when forced,
can exhibit a range of possible behaviours : B.
def Q
A typing context Γ denotes the set [[Γ]] = (x:A)∈Γ [[A]]. Then:
— A value Γ `v V : A denotes a function from [[Γ]] to [[A]].
— A computation Γ `c M : B denotes a relation from [[Γ]] to [[B]]. This is thought of as
relating ρ to b when b is a possible behaviour of M [ρ].
— A stack Γ `k K : B =⇒ C denotes a relation from [[Γ]] × [[B]] to [[C]]. This is thought of
as relating (ρ, b) to c when a CK-configuration consisting of a computation exhibiting
behaviour b and the stack K[ρ] may exhibit behaviour c.
The semantic equations are omitted, but note the following clause:
[[return V ]] = [[V ]]
using the fact that a function is a special kind of relation. Adequacy takes the form
[
[[M ]] = [[T ]]
M ⇓T
[[A]]w
w, and the diagram [[A]]w
x
/ [[A]]x commutes for all worlds w 6 x 6 y.
[[A]]x
y
[[A]]w
y #
[[A]]y
— A computation type B denotes a a functor from W op to Set. Spelling this out, for
each world w, we provide a set [[B]]w, thought of as a semantic domain for WSC-
configurations : B at w. For worlds w 6 x, we provide a function [[B]]w
x from [[B]]x
def
For a world w, write Sw = Bw for the set of states at w. The semantics of types is as
follows.
def P
[[F A]]w = x>w (Sx × [[A]]x) since an WSC-configuration : F A at w
evaluates to x, s, return V for a world x > w
and state s and closed value V : A at x.
def
[[A → B]]w = [[A]]w → [[B]]w since a WSC-configuration : A → B
pops a value : A and behaves as an WSC-configuration : B.
Q def Q Q
[[ i∈I B i ]]w = i∈I [[B i ]]w since a WSC-configuration : i∈I B i
pops i ∈ I and behaves as a WSC-configuration : B i .
def Q
[[U B]]w = x>w (Sx → [[B]]x) since a value V : U B at w,
when forced in any world x > w and state s at x,
gives a WSC-configuration x, s, force V at x.
def
A
Q typing context Γ denotes the functor given at the world w by the set [[Γ]]w =
(x:A)∈Γ [[A]]w, an element of which is a semantic environment. Then:
— A value w|Γ `v V : A denotes a family of functions ([[V ]]x : [[Γ]]x → [[A]]x)x>w that is
“natural” in the following sense: for any w 6 x 6 y, the square of functions
[[V ]]x
[[Γ]]x / [[A]]x
[[Γ]]x
y [[A]]x
y
[[Γ]]y / [[A]]y
[[V ]]y
[[Γ]]x
y ×[[B]]y '
[[Γ]]y × [[B]]y / [[C]]y
[[K]]y
5.6. Control
The denotational semantics for CBPV extended with control operators is rather subtle,
and I shall explain it using some imaginary notions. For any computation type B,
imagine a notion of idealized stack K from B that has no top-level type; this excludes
nil. Likewise, imagine a notion of an idealized CK-configuration, which consists of a
closed computation M : B and an idealized stack K from B. Although idealized stacks
and CK-configurations do not actually exist, these notions are conceptually helpful.
Let us now fix a set R, thought of as a semantic domain for idealized CK-
configurations. The semantics of types is arranged as follows.
— A value type A denotes a set, thought of as a semantic domain for values of type A.
— A computation type B denotes a set, thought of as a semantic domain for idealized
stacks from B.
The clauses are as follows.
def
[[F A]] = [[A]] → R since an idealized stack K from F A converts a value V : A
into an idealized CK-configuration return V, K.
def
[[A → B]] = [[A]] × [[B]] since an idealized stack V :: K from A → B consists of
a value V : A and an idealized stack K from B.
Q def P Q
[[ i∈I B i ]] = i∈I [[B i ]] since an idealized stack ı̂ :: K from i∈I B i consists of
ı̂ ∈ I and an idealized stack K from B i .
def
[[U B]] = [[B]] → R since a value V : U B, when forced alongside any idealized stack
K from B, gives an idealized CK-configuration force V, K.
Q
An ordinary context Γ denotes the set (x:A)∈Γ [[A]], an element of which is a semantic
def Q
environment. A stack context ∆ denotes the set [[∆]] = (α:B)∈Γ [[B]], an element of
which can be called an “idealized stack environment”. Then:
— A value Γ `v V : A | ∆ denotes a function from [[Γ]] × [[∆]] to [[A]].
— A computation Γ `c M : B denotes a function from [[Γ]] × [[∆]] × [[B]] to R. Intuitively,
given environments ρ ∈ [[Γ]] and ν ∈ [[∆]], and an idealized stack K from B, we obtain
an idealized CK-configuration M [ρ, ν], K.
Lift /
Recursion Cpo o ⊥ Cppo(
Inclusion
Free s-algebra
/
Interactive input Set o ⊥ Algs
Carrier
Inclusion /
Nondeterminism Set o ⊥ Rel
P
S×−
/
Global store Set o ⊥ Set
S→−
/
Dynamically generated store [W, Set] o ⊥ [W op , Set]
−→R
/
Control Set o ⊥ Setop
−→R
Free T -algebra
/
Monad T on Set Set o ⊥ SetT
Carrier
another category D that interprets computation types and stacks. The connectives U /F
and the computations are interpreted using the adjunction structure. Thus, whereas
Moggi [Moggi 1991] proposed to model effects using a certain kind of category V with a
monad, CBPV provides a more refined picture. For more details, see [Levy 2005; 2004].
Some examples are shown in Figure 7. Here we write Cpo for the category of cpos
and continuous functions; and Cppo( for the category of pointed cpos and strict
continuous functions; and Algs for the category of s-algebras and homomorphisms;
and Rel for the category of sets and relations; and [W, Set] for the category of func-
tors W → Set and natural transformations; and SetT for the category of Eilenberg-
Moore T -algebras and homomorphisms.
P In the case of dynamically generated store,
the left adjoint sends A to ( x>w (Sx × Ax))w∈W , and the right adjoint sends B to
Q
( x>w (Sx → Ax))w∈W .
Despite the omission of these advanced topics (and others), I hope that the diverse
semantics included in this article have made clear that, just as the simply typed λ-
calculus with all of its β- and η-laws is a fundamental calculus of pure functional pro-
gramming, so CBPV with its laws is a fundamental calculus of functional programming
with effects.