-
Notifications
You must be signed in to change notification settings - Fork 5
/
14-continuations-2.scala
379 lines (304 loc) · 12.5 KB
/
14-continuations-2.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/**
Automatic Transformation into CPS
=================================
In the previous lecture, we learned about continuation-passing
style (CPS), how it allows to treat execution order as a
first-class concept, and how to manually transform a program into
it. Today, we want to write an automatic transformation from an
FAE program in direct style into the equivalent program in
continuation-passing style. On the way, we will also learn how to
use the Scala type system to help us encode the specification of
a transformation like this.
*/
object transform {
/**
Precise typing
--------------
The transformation will be a Scala method that pattern matches
on FAE expressions, and creates other FAE expressions. The
signature of that method could be:
def cps(exp: Exp): Exp
Of course, the idea would be that `cps` always returns
expressions in continuation-passing style. To ensure that we
get the implementation of `cps` right, we would have to write a
test suite, or make a proof, or at least think hard about
whether our implement is correct. Instead of going this route,
we are going to define a set of case classes that exactly
encode FAE programs in CPS but no other FAE programs. We can
then use a signature such as the following for the
transformation:
def cps(exp: Exp): ContExp
The benefit will be that when we implement `cps`, the Scala
typechecker will ensure that we actually return an expression
in CPS, because all values of the type `ContExp` represent
expressions in CPS.
Source, target, and meta language
---------------------------------
When we think about a transformation between languages, it
helps to use different words for the different languages
involved:
- The *source language* is the language the input to the
transformation is written in. In the lecture today, the
source language is FAE.
- The *target language* is the language the output of the
transformation is written in. In the lecture today, the
target language is FAE in continuation-passing style.
- The *meta language* is the language the transformation
itself is written in. In the lecture today, the meta
language is Scala.
The source language
-------------------
Our source language is simply FAE, as defined in a previous
lecture. We copy the relevant definitions here:
*/
sealed abstract class Exp
case class Num(n: Int) extends Exp
case class Id(name: Symbol) extends Exp
case class Add(lhs: Exp, rhs: Exp) extends Exp
implicit def num2exp(n: Int) = Num(n)
implicit def id2exp(s: Symbol) = Id(s)
case class Fun(param: Symbol, body: Exp) extends Exp
case class App (funExpr: Exp, argExpr: Exp) extends Exp
/**
Target language
---------------
The target language should be the subset of the source language
that is in continuation-passing style, that is, the subset
where programs have the three properties from the previous
lecture:
1. All calls are in tail position.
2. All functions take continuation arguments.
3. No function returns a value.
To be able to formalize these properties in the Scala
typesystem, we reformulate them using the terms "non-trivial
expression" and "trivial expression" that we already used
informally in the previous lecture. Now it is time to define
them more formally:
- An expression is *non-trivial* if evaluating it might take
a long time, multiple steps, or might not
terminate. Example: Function calls.
- An expression is *trivial* if evaluating it is sure to be
instantaneous and always succeeds. Example: Integer
literals.
In CPS, trivial subexpressions will be left alone. In
particular, they will still return values. But every nontrivial
subexpressions needs to be changed so that it accepts and calls
a continuation instead of returning a value. So we can
reformulate the three properties of programs in
continuation-passing style as follows:
1. All non-trivial expressions are in tail position.
2. All functions take continuation arguments.
3. No trivial expressions are in tail position.
Note how the third property used to be semantic (what happens
at run time) and became syntactic (how programs look like). The
two versions of the third property still mean the same thing:
Only trivial expressions evaluate to values, but they cannot
appear in tail position, so the last thing a function does can
never be to return a value. The good thing about this more
syntactic reformulation is that we can encode it into the
syntax of the target language, that is, into the case classes
that we use to represent target language programs.
Trivial and nontrivial expression in the target language
--------------------------------------------------------
We start by saying that an expression in continuation-passing
style is either trivial or nontrivial:
*/
sealed abstract class ContExp
sealed abstract class TrivialContExp extends ContExp
sealed abstract class NontrivialContExp extends ContExp
/**
No we go through the five case classes of FAE and decide
whether they are trivial or nontrivial, and what there
subexpressions are.
We start with numeric literals. They are the paradigmatic
example of trivial expressions, so we let `ContNum` extend
`TrivialContExp`:
*/
case class ContNum(n: Int) extends TrivialContExp
/**
Next we look at identifier occurrences. Since we are in a
call-by-value setting, evaluating an identifier is
instantaneous and always terminates. We therefore classify
identifier occurrences as trivial. In a call-by-name or
call-by-need setting, we would have to treat them as nontrivial
instead!
*/
case class ContId(name: Symbol) extends TrivialContExp
/**
Now we consider addition. Evaluating an addition expression
only takes a single step, so we treat them as trivial and let
`ContAdd` extend `TrivialContExp`.
Evaluation of `ContAdd` will work by evaluating the
subexpressions first, and then doing the addition. So neither
of the subexpressions is in tail positions, so they have to be
trivial, by the third property of programs in CPS. We therefore
use `TrivialContExp` for both `lhs` and `rhs`.
*/
case class ContAdd(lhs: TrivialContExp, rhs: TrivialContExp)
extends TrivialContExp
// can also decide to ContAdd(...) extends NontrivialContExp
// TODO explain how to deal with nontrivial adds and 2nd req.
/**
The usual implicit conversions, for convenience:
*/
implicit def num2contexp(n: Int) = ContNum(n)
implicit def id2contexp(s: Symbol) = ContId(s)
/**
Next we have to think about application. Evaluating an
application means evaluating the subexpressions first and then
calling the function. So the subexpressions are not in tail
position and therefore have to be trivial by the third property
of programs in CPS. We therefore use `TrivalContExp` for
`funExpr` and `argExpr`.
By the second property of programs in CPS, all functions take
continuations as additional argument. So we also have to
provide a continuation in every function call. To ensure we
don't forget these continuation arguments, we add a field of
type `Continuation` to the `ContApp` case class. The type
`Continuation` will be defined below.
Finally, evaluating an application might take multiple steps or
a long term or might not even terminate, depending on what
happens inside the called function. We therefore classify
applications as nontrivial, and let `ContApp` extend
`NontrivialContExp`.
*/
case class ContApp(funExpr: TrivialContExp,
argExpr: TrivialContExp,
cont: Continuation)
extends NontrivialContExp
/**
Finally, we think about functions. The body of a function is in
tail position, so it has to be nontrivial by the third property
of programs in CPS. Accordingly, we choose `body:
NontrivialContExp`.
By the second property of programs in CPS, all functions take
continuations as additional argument. We add a field of type
`Symbol` to hold the name of the continuation parameter.
Function expressions itself are instantaneously evaluated to
closures, so they are trivial, and we let `ContFun` extend
`TrivialContExp`.
*/
case class ContFun(param: Symbol,
contParam: Symbol,
body: NontrivialContExp)
extends TrivialContExp
/**
In the examples above, subexpressions were usually required to
be trivial. But sometimes, subexpressions can be required to be
nontrivial, for example:
case class If(condExpr: TrivialContExp,
thenBranch: NontrivialContExp,
elseBranch: NontrivialContExp)
extends NontrivialContExp
By the first and third property of programs in CPS, whether a
subexpression needs to trivial or nontrivial depends on whether
it is in tail position or not.
Continuations in the target language
------------------------------------
Now in addition to trivial and nontrivial expressions, we also
need continuations in our target language. We already used the
type `Continuation` once above, in the type of a field of
`ContApp`.
*/
sealed abstract class Continuation extends ContExp
/**
There are two kinds of continuations in the target language:
Names of continuation arguments, and anonymous continuations
constructed by function expressions. The body of an anonymous
continuation has to be a nontrivial expression, since it is in
tail position.
*/
case class FunContinuation(
param: Symbol,
body: NontrivialContExp)
extends Continuation
case class IdContinuation(
name: Symbol)
extends Continuation
implicit def id2cont(name: Symbol) = IdContinuation(name)
/**
Finally, we also need to be able to *call* a
continuation. Since we don't know what the continuation will
do, calling a continuation is a nontrivial expression. And
since we first evaluate the continuation's argument and then
call the continuation, the argument is not in tail position and
therefore needs to be a trivial expression.
*/
case class AppContinuation(
cont: Continuation,
arg: TrivialContExp)
extends NontrivialContExp
/**
Fresh names
-----------
To avoid name clashes, we need to generate fresh names all the
time.
*/
var counter: Int = 0
def freshName(name: String) = {
counter += 1
Symbol(name + "_" + counter)
}
/**
The Transformation
------------------
Ok, now we can write a transformation of arbitrary
expressions into non-trivial expressions in CPS.
*/
def cps(e: Exp, k: Continuation): NontrivialContExp =
e match {
case Num(n) => AppContinuation(k, ContNum(n))
case Id(name) => AppContinuation(k, ContId(name))
case Add(lhs, rhs) => {
val x = freshName("x")
val y = freshName("y")
cps(lhs, FunContinuation(x,
cps(rhs, FunContinuation(y,
AppContinuation(k, ContAdd(x, y))))))
}
case Fun(param, body) =>
AppContinuation(k,
ContFun(param, 'dynk, cps(body, 'dynk)))
// in the function body, we have two continuations:
//
// k: continuation from where the lambda is
// (static continuation)
//
// dynk: continuation from where the application is
// (dynamic continuation)
case App (funExpr, argExpr) =>
cps(funExpr, FunContinuation('f,
cps(argExpr, FunContinuation('a,
ContApp('f, 'a, k)))))
}
/**
Note how the Scala type checker helps us get this right by
ensuring that we don't forget any continuation arguments or mix
up trivial and nontrivial expressions.
*/
}
import transform._
// Experiments show that this transformation works, but also that
// it creates unnecessary expressions of the form:
//
// AppContinuation(FunContinuation(name, body), value)
//
// 1. These expressions are called "administrative redexes".
//
// 2. We could write the administrative redexes as
//
// wthContinuation(name, value, body)
//
// with wthContinuation defined like wth from an earlier lecture.
//
// 3. The AppContinuation part of the administrative redexes are
// created in the cases for expressions that are already
// trivial (Num, Id, and Fun).
//
// 4. The FunContinuation part of the administrative redexes are
// created in the recursive calls of cps.
//
// => The administrative redexes are created because cps always
// creates a nontrivial expression in CPS, but sometimes a
// trivial expression in CPS would be shorter and more
// appropriate.