Optional 1: let is sugar
In Scheme, source code is data. Every non-atomic expression is written as a Scheme list, so we can write procedures that manipulate other programs just as we write procedures that manipulate lists.
Rewriting programs can be useful: we can write an interpreter that only handles a small core of the language, and then write a procedure that converts other special forms into the core language before a program is passed to the interpreter.
For example, the let
special form is equivalent to a call expression that begins with a lambda
expression. Both create a new frame extending the current environment and evaluate a body within that new environment.
(let ((a 1) (b 2)) (+ a b))
;; Is equivalent to:
((lambda (a b) (+ a b)) 1 2)
These expressions can be represented by the following diagrams:
Let | Lambda |
---|---|
Use this rule to implement a procedure called let-to-lambda
that rewrites all let
special forms into lambda
expressions. If we quote
a let expression and pass it into this procedure, an equivalent lambda
expression should be returned:
scm> (let-to-lambda '(let ((a 1) (b 2)) (+ a b)))
((lambda (a b) (+ a b)) 1 2)
scm> (let-to-lambda '(let ((a 1)) (let ((b a)) b)))
((lambda (a) ((lambda (b) b) a)) 1)
scm> (let-to-lambda 1)
1
scm> (let-to-lambda 'a)
a
In order to handle all programs, let-to-lambda
must be aware of Scheme syntax. Since Scheme expressions are recursively nested, let-to-lambda
must also be recursive. In fact, the structure of let-to-lambda
is somewhat similar to that of scheme_eval
--but in Scheme! As a reminder, atoms include numbers, booleans, nil, and symbols. You do not need to consider code that contains quasiquotation for this problem.
(define (let-to-lambda expr)
(cond ((atom? expr) <rewrite atoms>)
((quoted? expr) <rewrite quoted expressions>)
((lambda? expr) <rewrite lambda expressions>)
((define? expr) <rewrite define expressions>)
((let? expr) <rewrite let expressions>)
(else <rewrite other expressions>)))
Hint: Consider how you can use map
to convert let
forms in every element of a list to the equivalent lambda
form.
scm> (zip '((1 2) (3 4) (5 6)))
((1 3 5) (2 4 6))
scm> (zip '((1 2)))
((1) (2))
scm> (zip '())
(() ())
Hint 2: In this problem, it may be helpful to build a scheme list that evaluates to a special form (for instance, a lambda
expression). As a related example, the following code builds a scheme list that evaluates to the expression (define (f x) (+ x 1))
:
Test your implementation by running
Use Ok to test your code:
python ok -q optional_1
We used let
while defining let-to-lambda
. What if we want to run let-to-lambda
on an interpreter that does not recognize let?
We can pass let-to-lambda
to itself to rewrite itself into an equivalent program without let:
;; The let-to-lambda procedure
(define (let-to-lambda expr)
...)
;; A list representing the let-to-lambda procedure
(define let-to-lambda-code
'(define (let-to-lambda expr)
...))
;; A let-to-lambda procedure that does not use 'let'!
(define let-to-lambda-without-let
(let-to-lambda let-to-lambda-code))