about summary refs log tree commit diff
path: root/sicp/chapter4.scm
blob: 651ccffc45c9b044c3a0a05e0eb882d78c41ba15 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
; Exercise 4.11
(define (make-frame bindings) (cons '*frame* bindings))
(define (lookup-within-frame var frame) (assoc var (cdr frame)))
(define (add-binding-to-frame! var val frame)
  (set-cdr! frame (cons (cons var val) (cdr frame))))

(define the-empty-environment '())
(define (extend-environment bindings base-env)
  (cons (make-frame bindings) base-env))
(define (enclosing-environment env) (cdr env))
(define (first-frame env) (car env))

(define (define-variable! var val env)
  (let* ((frame (first-frame env))
         (binding (lookup-within-frame var frame)))
    (if binding
        (set-cdr! binding val)
        (add-binding-to-frame! var val frame))))
; Exercise 4.12
(define (lookup-binding variable environment)
  (let loop ((env environment))
    (if (eq? env the-empty-environment)
        #f
        (cond ((lookup-within-frame variable (first-frame env)))
              (else (loop (enclosing-environment env)))))))
(define (set-variable-value! var val env)
  (cond ((lookup-binding var env) => (lambda (b) (set-cdr! b val)))
        (else (error "Unbound variable: SET!" var))))
; Exercise 4.16.a
(define (lookup-variable-value var env)
  (let ((value (cond ((lookup-binding var env) => cdr)
                     (else '*unassigned*))))
    (if (eq? value '*unassigned*)
        (error "Unbound variable" var)
        value)))

; Exercise 4.3
(define (make-table)
  (let ((local-table (list '*table*)))
    (define (lookup key)
      (let ((record (assoc key (cdr local-table))))
        (if record (cdr record) #f)))
    (define (insert! key value)
      (let ((record (assoc key (cdr local-table))))
        (if record
            (set-cdr! record value)
            (set-cdr! (let tail ((table local-table))
                        (if (null? (cdr table)) table (tail (cdr table))))
                      (list (cons key value)))))
      'ok)
    (define (dispatch m)
      (cond ((eq? m 'lookup-proc) lookup)
            ((eq? m 'insert-proc!) insert!)
            (else (error "Unknown operation: TABLE" m))))
    dispatch))
(define anal-table (make-table))
(define (get-analyzer exp)
  (if (list? exp)
      ((anal-table 'lookup-proc) (car exp))
      #f))
(define (analyze exp)
  (cond ((get-analyzer exp) => (lambda (analyzer) (analyzer exp)))
        ((or (number? exp) (string? exp)) (lambda (env) exp))
        ((symbol? exp) (lambda (env) (lookup-variable-value exp env)))
        ((pair? exp) (analyze-application (cons 'call exp)))
        (else (error "Unknown expression type: ANALYZE" exp))))

(define (definition? exp) (and (list? exp) (eq? (car exp) 'define)))
(define (definition-variable exp)
  (if (symbol? (cadr exp))
      (cadr exp)
      (caadr exp)))
(define (definition-value exp)
  (if (symbol? (cadr exp))
      (caddr exp)
      (make-lambda (cdadr exp) (cddr exp))))
(define (analyze-definition exp)
  (let ((var (definition-variable exp))
        (vproc (analyze (definition-value exp))))
    (lambda (env)
      (define-variable! var (vproc env) env)
      'ok)))

(define (begin-actions exp) (cdr exp))
(define (last-exp? seq) (null? (cdr seq)))
(define (first-exp seq) (car seq))
(define (rest-exps seq) (cdr seq))
; Exercise 4.16
(define (define->set! exp)
  (if (definition? exp)
      (list 'set! (definition-variable exp) (definition-value exp))
      exp))
(define (analyze-sequence exps)
  (let ((vars (map definition-variable (filter definition? exps))))
    (if (null? vars)
        (let ((procs (map analyze exps)))
          (if (null? procs)
              (error "Empty sequence: ANALYZE")
              (let loop ((first-proc (car procs))
                         (rest-procs (cdr procs)))
                (if (null? rest-procs)
                    first-proc
                    (loop (lambda (env) (first-proc env) ((car rest-procs) env))
                          (cdr rest-procs))))))
        (analyze (list* (list* 'lambda vars (map define->set! exps))
                        (map (lambda (var) ''*unassigned*) vars))))))
(define (analyze-begin exp) (analyze-sequence (begin-actions exp)))

(define (primitive-procedure? p) (and (pair? p) (eq? (car p) 'primitive)))
(define (primitive-implementation p) (cadr p))

(define (make-procedure parameters body env)
  (list 'procedure parameters body env))
(define (compound-procedure? p) (and (pair? p) (eq? (car p) 'procedure)))
(define (procedure-parameters p) (cadr p))
(define (procedure-body p) (caddr p))
(define (procedure-environment p) (cadddr p))

(define (execute-application proc args)
  (cond ((primitive-procedure? proc)
         (apply (primitive-implementation proc) args))
        ((compound-procedure? proc)
         ((procedure-body proc)
          (extend-environment (map cons (procedure-parameters proc) args)
                              (procedure-environment proc))))
        (else (error "Unknown procedure type: EXECUTE-APPLICATION" proc))))

(define (operator exp) (cadr exp))
(define (operands exp) (cddr exp))
(define (no-operands? ops) (null? ops))
(define (first-operand ops) (car ops))
(define (rest-operands ops) (cdr ops))
(define (analyze-application exp)
  (let ((fproc (analyze (operator exp)))
        (aprocs (map analyze (operands exp))))
    (lambda (env)
      (execute-application (fproc env)
                           (map (lambda (aproc) (aproc env)) aprocs)))))

(define (analyze-quoted exp)
  (let ((qval (cadr exp)))
    (lambda (env) qval)))

(define (assignment-variable exp) (cadr exp))
(define (assignment-value exp) (caddr exp))
(define (analyze-assignment exp)
  (let ((var (assignment-variable exp))
        (vproc (analyze (assignment-value exp))))
    (lambda (env)
      (set-variable-value! var (vproc env) env)
      'ok)))

(define (make-lambda parameters body)
  (cons 'lambda (cons parameters body)))
(define (lambda-parameters exp) (cadr exp))
(define (lambda-body exp) (cddr exp))
(define (analyze-lambda exp)
  (let ((vars (lambda-parameters exp))
        (bproc (analyze-sequence (lambda-body exp))))
    (lambda (env) (make-procedure vars bproc env))))

(define analyzed-true (lambda (env) (lookup-variable-value 'true env)))
(define analyzed-false (lambda (env) (lookup-variable-value 'false env)))
(define (false? evaluated-exp env) (eq? evaluated-exp (analyzed-false env)))
(define (true? evaluated-exp env) (not (false? evaluated-exp env)))
(define (if-predicate exp) (cadr exp))
(define (if-consequent exp) (caddr exp))
(define (if-alternative exp)
  (if (null? (cdddr exp))
      'false
      (cadddr exp)))
(define (analyze-if exp)
  (let ((pproc (analyze (if-predicate exp)))
        (cproc (analyze (if-consequent exp)))
        (aproc (analyze (if-alternative exp))))
    (lambda (env) ((if (true? (pproc env) env) cproc aproc) env))))

; Exercise 4.4
(define (analyze-and exp)
  (let loop ((first-exp analyzed-true)
             (rest-exps (map analyze (cdr exp))))
    (if (null? rest-exps)
        first-exp
        (loop (lambda (env)
                (let ((pred (first-exp env)))
                  (if (false? pred env) pred ((car rest-exps) env))))
              (cdr rest-exps)))))
(define (analyze-or-exps analyzed-exps)
  (let loop ((first-exp analyzed-false) (rest-exps analyzed-exps))
    (if (null? rest-exps)
        first-exp
        (loop (lambda (env)
                (let ((pred (first-exp env)))
                  (if (false? pred env) ((car rest-exps) env) pred)))
              (cdr rest-exps)))))
(define (analyze-or exp) (analyze-or-exps (map analyze (cdr exp))))

; Exercise 4.5
(define (cond-clauses exp) (cdr exp))
(define (cond-predicate clause) (car clause))
(define (cond-actions clause) (cdr clause))
(define (analyze-cond-clause clause)
  (let ((pproc (analyze (cond-predicate clause)))
        (bproc (let ((actions (cond-actions clause)))
                 (cond ((null? actions) (lambda (pred env) pred))
                       ((eq? (car actions) '=>)
                        (let ((fproc (analyze (cadr actions))))
                          (lambda (pred env)
                            (execute-application (fproc env) (list pred)))))
                       (else (let ((consequences (analyze-sequence actions)))
                               (lambda (pred env) (consequences env))))))))
    (lambda (env)
      (let ((pred (pproc env)))
        (if (true? pred env) (bproc pred env) (analyzed-false env))))))
(define (analyze-cond exp)
  (analyze-or-exps (map analyze-cond-clause (cond-clauses exp))))

; Exercise 4.6
(define (let->combination exp)
  (let ((ad (cadr exp))
        (dd (cddr exp)))
    (if (list? ad)
        (list* (list* 'lambda (map car ad) dd)
               (map cadr ad))
        ; Exercise 4.8
        `((lambda (,ad)
            (set! ,ad (lambda ,(map car (car dd)) ,@(cdr dd)))
            (,ad ,@(map cadr (car dd))))
          '*unassigned*))))
; Exercise 4.22
(define (analyze-let exp) (analyze (let->combination exp)))

; Exercise 4.7
(define (let*->nested-lets exp)
  (let ((body (cddr exp)))
    (let loop ((bindings (cadr exp)))
      (if (or (null? bindings) (last-exp? bindings))
          (list* 'let bindings body)
          (list 'let (list (car bindings)) (loop (cdr bindings)))))))
(define (analyze-let* exp) (analyze (let*->nested-lets exp)))

; Exercise 4.9
(define (analyze-while exp) ; (while pred body)
  (let ((pproc (analyze (cadr exp)))
        (bproc (analyze-sequence (cddr exp))))
    (lambda (env)
      (let loop ((pred (pproc env)))
        (if (true? pred env)
            (begin (bproc env)
                   (loop (pproc env))))))))
(define (analyze-for exp) ; (for init pred body)
  (analyze (list 'let (cadr exp) (cons 'while (cddr exp)))))

; Exercise 4.2
(define (add-analyzer! tag analyzer)
  ((anal-table 'insert-proc!) tag analyzer))
(add-analyzer! 'call analyze-application)
(add-analyzer! 'quote analyze-quoted)
(add-analyzer! 'set! analyze-assignment)
(add-analyzer! 'define analyze-definition)
(add-analyzer! 'if analyze-if)
(add-analyzer! 'lambda analyze-lambda)
(add-analyzer! 'begin analyze-begin)
(add-analyzer! 'cond analyze-cond)
(add-analyzer! 'and analyze-and)
(add-analyzer! 'or analyze-or)
(add-analyzer! 'let analyze-let)
(add-analyzer! 'let* analyze-let*)
(add-analyzer! 'while analyze-while)
(add-analyzer! 'for analyze-for)

(define primitive-procedures
  (let ((procedures (list (cons 'first car)
                          (cons 'rest cdr)
                          (cons 'cons cons)
                          (cons 'null? null?)
                          (cons 'assoc assoc)
                          (cons 'display display)
                          (cons 'newline newline)
                          (cons '= =)
                          (cons '< <)
                          (cons '> >)
                          (cons '+ +)
                          (cons '- -)
                          (cons '* *)
                          (cons '/ /))))
    (map (lambda (p) (cons (car p) (list 'primitive (cdr p)))) procedures)))
(define (make-environment . environments)
  (if (null? environments)
      the-empty-environment
      (extend-environment (car environments)
                          (apply make-environment (cdr environments)))))
(define (mk-init-env)
  (make-environment primitive-procedures
                    (list (cons 'false #f) (cons 'true #t) (cons 'else #t))))

(define (evaluator-loop)
  (let loop ((environment (mk-init-env)))
    (display "> ")
    (let ((input (read)))
      (if (not (eq? input 'quit))
          (let ((output ((analyze input) environment)))
            (display output)
            (newline)
            (loop environment))))))

; Exercise 4.21
(define (factorial n)
  ((lambda (fact) (fact fact n))
   (lambda (ft k) (if (= k 1) 1 (* k (ft ft (1- k)))))))
(define (fibonacci n)
  ((lambda (fib) (fib fib n))
   (lambda (f k)
     (if (or (= k 0) (= k 1))
         k
         (+ (f f (1- k))
            (f f (- k 2)))))))
(define (f x)
  ((lambda (even? odd?) (even? even? odd? x))
   (lambda (ev? od? n) (if (= n 0) #t (od? ev? od? (1- n))))
   (lambda (ev? od? n) (if (= n 0) #f (ev? ev? od? (1- n))))))