~ Partial (1/2) evaluator (Reducer)
out = [[p]](i, i')
, thenp1 = [[mix]](p, i); out = [[p1]](i')
.- Alternatively, can write as
[[p]](i, i') = [[ [[mix]](p, i) ]](i')
- Let
S
be the source language (Early Rust). LetT
be the target language (assembly). LetL
be the language that the interpreter forS
is implemented in (OCaml). - See that we have the equation
out = [source]_S (in)
, orout = [interp]_L (source, in).
- That's the equation for the interpreter.
- a compiler produces a target program, so we have
target = [compiler]_L(source)
. Running the target and source programs should have the same effect, so[target]_T(in) = [source]_S(in)
. - Written differently, this is
[ [compiler]_L(source) ]_T(in) = [source]_S(in)
.
out = [source]_S (in)
out = [int](source, in)
out = [[mix](int, source)](in)
- But by definition of
target
, we haveout = target(in)
- Thus, we see that
target = [mix](int, source)
. We get the compiled output program/target program by partially applying the interpreter to the source program.
- Start with
target = [mix](int, source)
. - Now partially apply,
target = [mix(mix, int)](source)
. - But we know that
target = compiler(source)
. So we must have[mix(mix, int)] = compiler
. - A compiler is obtained by partially applying the partial applier against the interpreter. Thus, when fed an input, it partially evaluates the interpreter against any input, giving us a compiler.
- consider
cogen = [mix(mix, mix)]
, applied to an interpreter. - Let
comp = cogen(interp) = ([mix](mix, mix))(interp) = mix(mix, interp)
- Apply
comp(source)
. This gives uscomp(source) = mix(mix, interp)(source) = mix(interp, source) = target
. - Thus, we have create a compiler generator, which takes an interpreter and produces a compiler.
-
Suppose we have a high-level compiler in the language
S
, fromS
toT
. I will denote that ash : S(S → T)
. where the compiler ish
(for high), written in languageS
, fromS
toT
. -
We also have a low-level compiler written in
T
fromS
toT
, denoted byl : T(S → T)
, where the compiler isl
for low. -
Suppose the two versions agree, so
[h]_S = [l]_T
. -
Suppose we extend
h
toh'
, to compile the languageS'
toT
.h'
is also written inS
, so we haveh': S(S'→ T)
. -
Now we can use
t
onh'
to recieve anS'
compilerl' : T(S' → T)
. -
We continue this process, to arrive at the following equations:
-
The above equations is more easily understood via ASCII art:
-
This shows how one can bootstrap/self-apply a compiler, and these types of diagrams will help us reason about self application later on, in the context of partial application.
- Let
$T_p(s, d)$ be the time taken to compute$[p](s, d)$ . - Let
$p_s$ be the result of specializing$p$ to$s$ .
- Partial evaluation and automatic program generation by Neil D Jones, Carsten K Gomard and Peter Setsoft