Lecture #19, Tuesday, March 11th ================================ - Is this really a compiler? ------------------------------------------------------------------------ ## 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 `list`s 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: * https://medium.com/graalvm/writing-truly-memory-safe-jit-compilers-f79ad44558dd