lecture 2: partial evaluation and types in self ...pe.pdf · assume program p computes an...

45
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.

Upload: others

Post on 08-Sep-2019

1 views

Category:

Documents


0 download

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 —

TYPES DURING COMPILER GENERATION: 3

We just showed that:

[[compiler]] : τS→τ

Furthermore τ was arbitrary, so

[[compiler]] : ∀τ . τS→τ

By similar reasoning (too big a tree to show!):

[[cogen]] : ∀α∀β . α→β→α→β

— 45 —