PL: Lecture #19  Tuesday, March 11th

Is this really a compiler?

Yes, it is, though it’s hard to see it when we’re compiling TOY code directly to Racket closures.

Another way to see this in a more obvious way is to change the compiler code so instead of producing a Racket closure it spits out the Racket code that makes up these closures when evaluated.

The basic idea is to switch from a function that has code that “does stuff”, to a function that emits that code indtead. For example, consider a function that computes the average of two numbers

(define (average x y)
  (/ (+ x y) 2))

to one that instead returns the actual code

(define (make-average-expression x y)
  (string-append "(/ (+ " x " " y ") 2)"))

It is, however, inconvenient to use strings to represent code: S-expressions are a much better fit for representing code:

(define (make-average-expression x y)
  (list '/ (list '+ x y) 2))

This is still tedious though, since the clutter of lists and quotes makes it hard to see the actual code that is produced. It would be nice if we could quote the whole thing instead:

(define (make-average-expression x y)
  '(/ (+ x y) 2))

but that’s wrong since we don’t want to include the x and y symbols in the result, but rather their values. Racket (and all other lisp dialects) have a tool for that: quasiquote. In code, you just use a backquote ` instead of a ', and then you can unquote parts of the quasi-quoted code using , (which is called unquote). (Later in the course we’ll talk about these "`"s and ","s more.)

So the above becomes:

(define (make-average-expression x y)
  `(/ (+ ,x ,y) 2))

Note that this would work fine if x and y are numbers, but they’re now essentially arguments that hold expression values (as S-expressions). For example, see what you get with:

(make-average-expression 3 `(/ 8 2))

Back to the compiler, we change the closure-generating compiler code

[(Bind names exprs bound-body)
(define compiled-body (compile bound-body))
(define compiled-exprs (map compile exprs))
(lambda ([env : ENV])
  (compiled-body (extend ...)))]

into

[(Bind names exprs bound-body)
(define compiled-body (compile bound-body))
(define compiled-exprs (map compile exprs))
`(lambda ([env : ENV])
    (,compiled-body (extend ...)))]

Doing this systematically would result in something that is more clearly a compiler: the result of compile would be an S-expression that you can then paste in the Racket file to run it.

An example of this idea taken seriously is the graal + truffle combination for implementing fast JIT compiled languages: