Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Futamura Projections

Futamura’s 1971 observation: specializing an interpreter to a source program yields a compiled program (the first projection); specializing the specializer to the interpreter yields a compiler (the second); specializing the specializer to itself yields a compiler generator (the third). The projections are usually stated against a standalone specializer. In Pink there is no such machine — specialization is evaluation under pink-evalc — so the projections become one-liners, and lib/purple.naj runs two of them at every session boot:

(let compiler       (run 0 ((pink-evalc pink-eval-src)  nil-env))
(let evalc-compiled (run 0 ((pink-evalc pink-evalc-src) nil-env))

The previous chapter was the first projection: pink-evalc applied to factorial’s source produced compiled factorial. Here the program being compiled is the evaluator itself. compiler is pink-eval with the interpretive overhead of the evaluator that processed it removed; evalc-compiled is the same treatment applied to pink-evalc — a compiled compiler. The session’s compile helper is a thin wrapper over the latter:

(let compile
  (lambda _ src (run 0 ((evalc-compiled src) nil-env)))

Both artifacts behave like what they were compiled from:

(define fact-src '(lambda f n (if (eq? n 0) 1 (* n (f (- n 1))))))
(define fi ((compiler fact-src) nil-env))
(fi 6)
;=> 720
((compile fact-src) 5)
;=> 120

Measuring collapse

The paper’s title claim is about towers: stack interpreters on interpreters, and staging collapses the whole stack. The witness is the residual code. Compile factorial directly — one level of evaluation:

(let pink-poly-src (car (read-file 'lib/pink-forms.naj))
(let pink-tie-src `(let pink-poly ,pink-poly-src
                     (lambda eval l (lambda _ e (((pink-poly eval) l) e))))
(let pink-tie (evalms nil (trans pink-tie-src nil))
(let pink-evalc (pink-tie (cons (lambda _ e (lift e)) 0))
(let fac '(lambda f n (if (eq? n 0) 1 (* n (f (- n 1)))))
  ((pink-evalc fac) (lambda _ y 0)))))))
;=> #<code 25 nodes>

Now put an interpreter in the way: run pink-evalc’s source through pink-eval, and apply the resulting (interpreted) compiler to factorial — two levels:

(let pink-poly-src (car (read-file 'lib/pink-forms.naj))
(let pink-tie-src `(let pink-poly ,pink-poly-src
                     (lambda eval l (lambda _ e (((pink-poly eval) l) e))))
(let pink-tie (evalms nil (trans pink-tie-src nil))
(let pink-eval (pink-tie (cons (lambda _ e e) 0))
(let pink-evalc-src `(,pink-tie-src (cons (lambda _ e (lift e)) 0))
(let fac '(lambda f n (if (eq? n 0) 1 (* n (f (- n 1)))))
(let nil-env (lambda _ y 0)
  ((((pink-eval pink-evalc-src) nil-env) fac) nil-env))))))))
;=> #<code 25 nodes>

Three levels — an interpreter interpreting an interpreter interpreting a compiler:

(let pink-poly-src (car (read-file 'lib/pink-forms.naj))
(let pink-tie-src `(let pink-poly ,pink-poly-src
                     (lambda eval l (lambda _ e (((pink-poly eval) l) e))))
(let pink-tie (evalms nil (trans pink-tie-src nil))
(let pink-eval (pink-tie (cons (lambda _ e e) 0))
(let pink-eval-src `(,pink-tie-src (cons (lambda _ e e) 0))
(let pink-evalc-src `(,pink-tie-src (cons (lambda _ e (lift e)) 0))
(let fac '(lambda f n (if (eq? n 0) 1 (* n (f (- n 1)))))
(let nil-env (lambda _ y 0)
  ((((((pink-eval pink-eval-src) nil-env) pink-evalc-src) nil-env) fac) nil-env)))))))))
;=> #<code 25 nodes>

Twenty-five nodes at every height. Each added interpreter costs evaluation time at generation, and contributes nothing to the residual — the tower collapses to the program at its top. Nothing in the language grows with the height either: the chains above are just longer applications.

Where the run goes

One discipline governs all of this, inherited from Part II’s closing note: code values are meaningful only inside the staging context that made them, so generation must happen inside the run that will execute it. In the floor blocks above, run 0 (or the top-level printer’s implicit reify) encloses the whole pink-evalc application. compile exists to package exactly that enclosure — which is why, at the session prompt, compiling goes through compile rather than through run-compiled applied to code built at the prompt: a code value produced by one prompt input refers to a generation context that is no longer open by the time another input tries to run it.