lecture 2: partial evaluation and types in self ...pe.pdf · assume program p computes an...
TRANSCRIPT
Lecture 2: Partial evaluation and Types in Self-application
Neil D. Jones
DIKU, University of Copenhagen (prof. emeritus)
Source: Partial Evaluation and Automatic Program Generation,
Neil Jones, Carsten Gomard and Peter Sestoft (Prentice-Hall 1993)
Downloadable from our web pages.
I: QUICK REVIEW OF 1. ORDER PARTIAL EVALUATION
Programs are data objects in a first order data domain D, for example
D = Atom ∪D ×D
A programming language L is a set L-programs together with a semantic
function
[[ ]]L : L-programs→ D ⇀ D
Program meanings are partial functions:
[[p]]L : D ⇀ D
(Omit L if it is clear from context.)
Examples for L = Scheme or Lisp:
[[(quote 37)]]L = 37
[[(lambda (x) (+ x x))]]L 3 = 6
— 2 —
INTERPRETER, COMPILER, PARTIAL EVALUATOR
An interpreter int (for S written in L) must satisfy:
[[source]]S(d) = [[int]](source, d)
A compiler comp (from S to T, written in L)
[[source]]S(d) = [[[[comp]](source)]]T(d)
A partial evaluator (for L) is a program spec satisfying, for any program p
and data s, d:
[[p]](s, d) = [[[[spec]](p, s)]](d)
— 3 —
TECHNIQUES FOR PARTIAL EVALUATION
I Applying base functions to known data
I unfolding function calls
I creating one or more specialised program points
Example. Ackermann’s function with known n = 2:
a(m,n) = if m=0 then n+1 else
if n=0 then a(m-1,1)
else a(m-1,a(m,n-1))
Specialised program:
a2(n) = if n=0 then 3 else a1(a2(n-1))
a1(n) = if n=0 then 2 else a1(n-1)+1
Less than half as many arithmetic operations as the original: since all tests
on m and computations involving m have been removed.
— 4 —
SPECIALISING ACKERMANN’S FUNCTION
a(m,n) = if m =? 0 then n+1 else
if n =? 0 then a(m-1,1)
else a(m-1,a(m,n-1))
Suppose we know m = 2, but n is unknown.
1. Symbolic evaluation: eliminate test on m.
a(2,n) = if n=?0 then a(1,1) else a(1,a(2,n-1))
2. Unfold call a(1,1), then symbolic evaluation:
a(1,1) = a(0,a(1,0))
3. Unfolding call a(1,0), then symbolic evaluation
a(1,0) = a(0,1) = 1+1 = 2
— 5 —
SPECIALISING ACKERMANN’S FUNCTION (CTD.)
4. From Steps 2 and 3 we get
a(1,1) = a(0,a(1,0)) = a(0,2) = 3 and so from Step 1:
a(2,n) = if n =? 0 then 3 else a(1,a(2,n-1))
5. Similarly:
a(1,n) = if n=?0 then a(0,1) else a(0,a(1,n-1))
6. Unfolding calls with m=0 gives simpler program:
a(2,n) = if n =? 0 then 3 else a(1,a(2,n-1))
a(1,n) = if n =? 0 then 2 else a(1,n-1) + 1
7. New specialised program points a1, a2:
a2(n) = if n =? 0 then 3 else a1(a2(n-1))
a1(n) = if n =? 0 then 2 else a1(n-1)+1
— 6 —
DIAGRAMMATIC NOTATION FOR PROGRAM RUNS
Diagrammatic notation:
Data value����
Program to be run
Data input to program -
Data output from program --
A 1-step run of program p
Inputdata 1
'
&
$
%@@@R
. . .
'
&
$
%?
Inputdata n
'
&
$
%���
programp
??
Outputdata
'
&
$
%— 7 —
PROGRAMS AS DATA: INTERPRETATION
Assume program p computes an input-output function.
Definition An interpreter for programming language S written in language
L is an L-program int such that for any S-program s and input in:
[[p]]S(d) = [[int]]L(p, d)
Interpretation: 1 step
S-sourceprogram
p
'
&
$
%?
runtimeinput d
'
&
$
%-
interpreterint
-- output
'
&
$
%
— 8 —
A COMPILER, DIAGRAMMATICALLY
Compilation: 2 steps
sourceprogramsource
'
&
$
%?
comp
??
runtimeinput
'
&
$
%-
targetprogramtarget
'
&
$
%-- output
'
&
$
%
— 9 —
SPECIALISATION
input d
��
��
?program
p
??
input s����-
output
'&$%
= data� � = program
subjectprogram p
��
��
?programspecialiser“spec”
??
stage 1input s
����-
stage 2input d
����- specialised
program ps
'&
$%-- output
����
Figure 1.1: Execution: normal; and with a program specialiser.
Specialisation is a staging transformation. Program p’s computation is not
performed all at once on (s,d), but rather in two stages.
1. A program transformation: given p and s, yield as output a specialised
program ps.
2. Program ps is run with the single input d.
— 10 —
THE FUTAMURA PROJECTIONS
1. A partial evaluator can compile:
targetdef= [[spec]](int, source)
2. A partial evaluator can generate a compiler:
compdef= [[spec]](spec, int)
3. A partial evaluator can generate a compiler generator:
cogendef= [[spec]](spec, spec)
— 11 —
CORRECTNESS OF THE FUTAMURA PROJECTIONS
Simple equational reasoning to verify:
1. [[target]](d) = [[source]]S(d)
2. target = [[comp]](source)
3. comp = [[cogen]](int)
(Surprise! It works well on the computer too... )
Practice: tricky it took a year to get right the first time, in 1984.)
— 12 —
COMPILING BY SPECIALISING AN INTERPRETER
Interpreter ∀program src ∀ data in . [[src]]S(in) = [[int]](src,in)
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to compile
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
targetdef= [[spec]](int, source)
To show: for any input data in,
[[source]]S(in) = [[target]](in)
Reasoning:
— 13 —
COMPILING BY SPECIALISING AN INTERPRETER
Interpreter ∀program src ∀ data in . [[src]]S(in) = [[int]](src,in)
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to compile
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
targetdef= [[spec]](int, source)
To show: for any input data in,
[[source]]S(in) = [[target]](in)
Reasoning:
out = [[source]]S (in) Running source S-program
— 14 —
COMPILING BY SPECIALISING AN INTERPRETER
Interpreter ∀program src ∀ data in . [[src]]S(in) = [[int]](src,in)
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to compile
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
targetdef= [[spec]](int, source)
To show: for any input data in,
[[source]]S(in) = [[target]](in)
Reasoning:
out = [[source]]S (in) Running source S-program
= [[int]](source,in) Definition of an S-interpreter
— 15 —
COMPILING BY SPECIALISING AN INTERPRETER
Interpreter ∀program src ∀ data in . [[src]]S(in) = [[int]](src,in)
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to compile
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
targetdef= [[spec]](int, source)
To show: for any input data in,
[[source]]S(in) = [[target]](in)
Reasoning:
out = [[source]]S (in) Running source S-program
= [[int]](source,in) Definition of an S-interpreter
= [[[[spec]](int,source)]] (in) Definition of specializer
— 16 —
COMPILING BY SPECIALISING AN INTERPRETER
Interpreter ∀program src ∀ data in . [[src]]S(in) = [[int]](src,in)
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to compile
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
targetdef= [[spec]](int, source)
To show: for any input data in,
[[source]]S(in) = [[target]](in)
Reasoning:
out = [[source]]S (in) Running source S-program
= [[int]](source,in) Definition of an S-interpreter
= [[[[spec]](int,source)]] (in) Definition of specializer
= [[target]](in) Definition of target
— 17 —
COMPILER GENERATION BY
SPECIALISING THE SPECIALISER
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
compilerdef= [[spec]](spec, int)
To show: for any source program source,
[[compiler]](source) = target
Reasoning:
— 18 —
COMPILER GENERATION BY
SPECIALISING THE SPECIALISER
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
compilerdef= [[spec]](spec, int)
To show: for any source program source,
[[compiler]](source) = target
Reasoning:
target = [[spec]] (int,source) You just saw it (next-last slide!)
— 19 —
COMPILER GENERATION BY
SPECIALISING THE SPECIALISER
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
compilerdef= [[spec]](spec, int)
To show: for any source program source,
[[compiler]](source) = target
Reasoning:
target = [[spec]] (int,source)
= [[[[spec]](spec,int)]] (source) Definition of specializer
— 20 —
COMPILER GENERATION BY
SPECIALISING THE SPECIALISER
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler
I From the interpreter’s input language
I To the specialiser’s output language
Suppose
compilerdef= [[spec]](spec, int)
To show: for any source program source,
[[compiler]](source) = target
Reasoning:
target = [[spec]] (int,source)
= [[[[spec]](spec,int)]] (source)
= [[compiler]](source) Definition of compiler
— 21 —
COMPILER GENERATOR GENERATION BY
SELF-APPLICATION
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler generator
Suppose
cogendef= [[spec]](spec, spec)
To show: for any interpreter int,
[[cogen]](int) = compiler
Reasoning:
— 22 —
COMPILER GENERATOR GENERATION BY
SELF-APPLICATION
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler generator
Suppose
cogendef= [[spec]](spec, spec)
To show: for any interpreter int,
[[cogen]](int) = compiler
Reasoning:
compiler = [[spec]] (spec,int) You just saw it (next-last slide!)
— 23 —
COMPILER GENERATOR GENERATION BY
SELF-APPLICATION
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler generator
Suppose
cogendef= [[spec]](spec, spec)
To show: for any interpreter int,
[[cogen]](int) = compiler
Reasoning:
compiler = [[spec]] (spec,int)
= [[[[spec]](spec,spec)]] (int) Definition of specializer
— 24 —
COMPILER GENERATOR GENERATION BY
SELF-APPLICATION
Par. evaluator ∀program p ∀data s, d . [[p]](s,d) = [[[[spec]](p,s)]](d)
We can use spec to generate a compiler generator
Suppose
cogendef= [[spec]](spec, spec)
To show: for any interpreter int,
[[cogen]](int) = compiler
Reasoning:
compiler = [[spec]] (spec,int)
= [[[[spec]](spec,spec)]] (int)
= [[cogen]](int) Definition of cogen
— 25 —
II. UNDERBAR TYPES FOR PARTIAL EVALUATION
Isn’t there a type error somewhere?
Tension: it seems that self-application f(f) requires f -type
A = A→ A (??)
Resolution: Work with symbolic operations.
A symbolic version
I of an operation op on values is a corresponding
I corresponding operation op on program texts.
Symbolic composition of programs p, q yields a program r.
Requirement: The meaning of r =
the composition of the meanings of p and q.
Partial evaluation =
the symbolic specialisation of a function
to a known first argument value.
— 26 —
REMAINDER OF THIS TALK
I A notation for the types of symbolic operations. The notation distin-
guishes
• the types of values from
• the types of program texts
I Natural definitions of type correctness of a first-order interpreter, com-
piler or partial evaluator.
— 27 —
UNDERBAR TYPES FOR SYMBOLIC COMPUTATION
t: type ::= firstorder | type× type | type→type
| typeX
Type firstorder describes values in D.
For each language X and type t, there is a type constructor
tX
Meaning: the set of X-programs that denote values of type t.
Examples
I The atomic value 37 has type firstorder
I Lisp program (quote 37) has type firstorderLisp
— 28 —
THE MEANING OF TYPE EXPRESSION T IS [[T ]]
[[firstorder]] = D
[[t1→t2]] = [[[t1]] → [[t2]]]
[[t1 × t2]] = {(t1, t2) | t1 ∈ [[t1]], t2 ∈ [[t2]]}
[[ tX
]] = { p ∈ D | [[p]]X∈ [[t]] }
Some type inference rules:
exp1 : t2 →t1, exp2 : t2exp1exp2 : t1
exp : tX
[[exp]]X : t
firstordervalue : firstorder
exp : tX
exp : firstorder
— 29 —
SYMBOLIC COMPOSITION
(α→ γ)(α→ β)× (β → γ)
(α→ γ)(α→ β)× (β → γ)
[[-]] × [[-]]
;
[[-]]
;
-
-
??'
&
$
%
'
&
$
%
'
&
$
%
'
&
$
%
(α→ β) = the set of all programs that
compute a function from α to β.
— 30 —
COMPOSITION OF FINITE TRANSDUCERS
M ; NNM
6
a/c- (q, s)(p, r)
6
r sb/c
'
&
$
%
'
&
$
%-a/b qp
�
-
'
&
$
%
cacba
���
@@@ -----
Point: no intermediate symbol b is ever produced.
— 31 —
COMPOSITION OF PROGRAMS
Consider composition oneto ; squares ; sum where
oneto(n) = [n, n− 1, . . . , 2, 1]
squares[a1, a2, . . . an] = [a21, a
22, . . . , a
2n]
sum[a1, a2, . . . an] = a1 + a2 + . . .+ an.
Straightforward program:
f(n) = sum(squares(oneto(n)))
squares(l) = if l = [] then [] else
cons(head(l)**2, squares(tail(l)))
sum(l) = if l = [] then 0 else
head(l) + sum(tail(l))
oneto(n) = if n = 0 then [] else cons(n, oneto(n-1))
Result of “deforestation” is a faster composed program:
g(n) = if n = 0 then 0 else n**2+g(n-1)
— 32 —
PARTIAL EVALUATION
(β → γ)(α× β → γ)× α
(β → γ)(α× β → γ)× α
[[-]] × Identity
fcn-spec
[[-]]
pgm-spec
-
-
??'
&
$
%
'
&
$
%
'
&
$
%
'
&
$
%
— 33 —
A BETTER DEFINITION OF PARTIAL EVALUATION
Type in the diagram:
pgm-spec : (α× β → γ × α)→ (β → γ)
First Curry the specialiser:
α→(β→γ)→α→β→γ
Then generalize:
spec : ∀α .∀τ . α→ τ → α→ τ
Usually α must be first order.
Definition. Program spec ∈ D is a partial evaluator if for all p, s ∈ D,
[[p]] s = [[[[spec]] p s]]
— 34 —
INTERPRETERS, COMPILERS, ETC.
REVISITED WITH A TASTE OF CURRY
An interpreter int (for S written in L) must satisfy:
[[source]]S = [[int]]source
A compiler comp (from S to T, written in L)
[[source]]S = [[[[comp]]source]]T
A partial evaluator (for L) is a program spec satisfying, for any program p
and data s:
[[p]] s = [[[[spec]] p s]]
— 35 —
TYPE INFERENCE FOR SELF-APPLICATION
The Futamura projections:
[[spec]] int sourcedef= target
[[spec]] spec intdef= compiler
[[spec]] spec specdef= cogen
Do these type-check?
Recall our type inference rules:
exp1 : t2 →t1, exp2 : t2exp1exp2 : t1
exp : tX
[[exp]]X : t
firstordervalue : firstorder
exp : tX
exp : firstorder
— 36 —
TYPES OF INTERPRETERS, ETC.
1. Type of source : τS
2. Type of [[int]] : ∀τ . τS→ τ
3. Type of [[compiler]] : ∀τ . τS→ τ
T
Remark: Line 3 gives
I the type of the compiling function.
I On the other hand, the type of the compiler text is:
compiler : ∀τ . τS→ τ
T
and similarly for source, int, spec.
4. Type of [[spec]]: ∀α .∀β . α→ β → α→ β
where α is first order
— 37 —
TYPES DURING COMPILATION 1
We wish to find the type of
targetdef= [[spec]] int source
Assume program source has type τS
. A deduction:
[[spec]] : ρ→σ→ρ→σ
[[spec]] : ?int : τ
S→τ
[[spec]] int : ?source : τ
S
[[spec]] int source : ?
— 38 —
TYPES DURING COMPILATION 2
We wish to find the type of
targetdef= [[spec]] int source
Assume program source has type τS
. A deduction:
[[spec]] : ρ→σ→ρ→σ
[[spec]] : τS→τ→τ
S→τ
int : τS→τ
[[spec]] int : ?source : τ
S
[[spec]] int source : ?
— 39 —
TYPES DURING COMPILATION 3
We wish to find the type of
targetdef= [[spec]] int source
Assume program source has type τS
. A deduction:
[[spec]] : ρ→σ→ρ→σ
[[spec]] : τS→τ→τ
S→τ
int : τS→τ
[[spec]] int : τS→τ
source : τS
[[spec]] int source : ?
— 40 —
TYPES DURING COMPILATION 4
We wish to find the type of
targetdef= [[spec]] int source
Assume program source has type τS
. A deduction:
[[spec]] : ρ→σ→ρ→σ
[[spec]] : τS→τ→τ
S→τ
int : τS→τ
[[spec]] int : τS→τ
source : τS
[[spec]] int source : τ
— 41 —
TYPES DURING COMPILATION: CONCLUSION
Thus
I if source has type
τS
I then target has type
τ = τL
(as expected – a compiler should be type-preserving).
The deduction uses only the type inference rules and generalization of poly-
morphic variables.
— 42 —
TYPES DURING COMPILER GENERATION: 1
Recall that: compilerdef= [[spec]] spec int where
interpreter int has type ∀τ . τS→τ .
We show: If p has type α→β
then [[spec]] spec p has type α→β
Deduction:
[[spec]] : ρ→σ→ρ→σ
[[spec]] : α→β→α→β →α→β→α→βspec : α→β→α→β
[[spec]] spec : α→β→α→βp : α→β
[[spec]] spec p : α→β
— 43 —
TYPES DURING COMPILER GENERATION: 2
Recall that:
compilerdef= [[spec]] spec int
where interpreter int has type ∀τ . τS→τ .
We just showed: If p has type α→β
then [[spec]] spec p has type α→β
Substituting α = τS, β = τ , we get
compiler = [[spec]] spec int : τS→τ
and so (as desired)
[[compiler]] : τS→τ
— 44 —