#lang racket
(require racket/mpair)
(provide (all-defined-out))
;; This is the repl program for section 4.2.3. It implements lazy-lists by
;; installing the lazy compound procedures my-cons, my-car, and my-cdr. Quotation
;; has been modified according to exercise 4.33 to produce lazy lists, and the driver
;; loop has been modified according to exercise 4.34 to make lazy lists print nicely.
;; Use "4-2-3-lazy-lists-repl-test.rkt" to test basic functionality.
;; PROGRAM SECTIONS:
;; 1. my-eval and my-apply
;; 2. eval procedures
;; 3. self-evaluating expressions, variables, and quotations
;; 4. definition and assignment
;; 5. thunks, lambdas, procedures and applications
;; 6. sequences and begin expressions
;; 7. boolean expressions
;; 8. if and cond expressions
;; 9. let, let*, and named-let
;; 10. environment and frames
;; 11. primitive procedures and the global environment
;; 12. repl operations
;; 13. lazy list operations
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 1. MY-EVAL AND MY-APPLY ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (my-eval expr env)
(cond [(self-evaluating? expr) expr]
[(variable? expr) (lookup-variable-value expr env)]
[(quoted? expr) (text-of-quotation expr env)]
[(definition? expr) (eval-definition expr env)]
[(assignment? expr) (eval-assignment expr env)]
[(if? expr) (eval-if expr env)]
[(cond? expr) (my-eval (cond->if expr) env)]
[(begin? expr) (eval-sequence (begin-actions expr) env)]
[(and? expr) (eval-and expr env)]
[(or? expr) (eval-or expr env)]
[(let? expr) (my-eval (let->combination expr) env)]
[(let*? expr) (my-eval (let*->nested-lets expr) env)]
[(named-let? expr) (my-eval (named-let->sequence expr) env)]
[(lambda? expr)
(make-procedure (lambda-parameters expr)
(lambda-body expr)
env)]
[(application? expr)
(my-apply (actual-value (operator expr) env)
(operands expr)
env)]
[else (error "Unknown expression type -- MY-EVAL" expr)]))
(define (my-apply procedure arguments env)
(cond [(primitive-procedure? procedure)
(apply-primitive-procedure
procedure
(list-of-arg-values arguments env))]
[(compound-procedure? procedure)
(eval-sequence
(procedure-body procedure)
(extend-environment
(procedure-parameters procedure)
(list-of-delayed-args arguments env)
(procedure-environment procedure)))]
[else (error "Unknown procedure type -- APPLY" procedure)]))
;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2. EVAL PROCEDURES ;;
;;;;;;;;;;;;;;;;;;;;;;;;;
(define (eval-definition expr env)
(define-variable! (definition-variable expr)
(my-eval (definition-value expr) env)
env)
'ok)
(define (eval-assignment expr env)
(set-variable-value! (assignment-variable expr)
(my-eval (assignment-value expr) env)
env)
'ok)
(define (list-of-values exprs env)
(if (no-operands? exprs)
empty
(cons (my-eval (first-operand exprs) env)
(list-of-values (rest-operands exprs) env))))
(define (eval-sequence exprs env)
(cond [(last-exp? exprs) (my-eval (first-exp exprs) env)]
[else (my-eval (first-exp exprs) env)
(eval-sequence (rest-exps exprs) env)]))
(define (eval-if expr env)
(if (true? (actual-value (if-predicate expr) env))
(my-eval (if-consequent expr) env)
(my-eval (if-alternative expr) env)))
(define (eval-and expr env)
(define ops (and-operands expr))
(cond [(empty? ops) 'true] ; (and) should be true
[else
(define val (my-eval (first ops) env))
(define rest-ops (rest ops))
(cond [(empty? rest-ops) val] ; (and x) should be x
[(false? val) false]
[else
(eval-and (make-and rest-ops) env)])]))
(define (eval-or expr env)
(define ops (or-operands expr))
(cond [(empty? ops) 'false] ; (or) should be false
[else
(define val (my-eval (first ops) env))
(define rest-ops (rest ops))
(cond [(empty? rest-ops) val] ; (or x) should be x
[(not (false? val)) val]
[else
(eval-or (make-or rest-ops) env)])]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. SELF-EVALUATING EXPRESSIONS, VARIABLES, AND QUOTATIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (tagged-list? expr tag)
(or (and (pair? expr) (eq? (first expr) tag))
(and (mpair? expr) (eq? (mcar expr) tag))))
;; Only numbers and strings are self-evaluating.
(define (self-evaluating? expr)
(or (number? expr)
(string? expr)))
(define (variable? expr)
(symbol? expr))
;; Quotations have the form: (quote <text-of-quotation>)
(define (quoted? expr)
(tagged-list? expr 'quote))
(define (text-of-quotation expr env)
(define toq (second expr))
(if (pair? toq)
(my-eval (list->lazy-list toq) env)
toq))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 4. DEFINITION AND ASSIGNMENT ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Variable definitions have the form: (define <var> <value>)
;; Procedure definitions have the form:
;; (define (<var> <parameter-1> ... <parameter-n>) <body>)
;; which is equivalent to:
;; (define <var> (lambda (<parameter-1> ... <parameter-n>) <body>))
(define (definition? expr) (tagged-list? expr 'define))
(define (definition-variable expr)
(if (symbol? (second expr))
(second expr)
(first (second expr))))
(define (definition-value expr)
(if (symbol? (second expr))
(third expr)
(make-lambda (rest (second expr)) ; formal parameters
(drop expr 2))))
;; Assignments have the form: (set! <var> <value>)
(define (assignment? expr)
(tagged-list? expr 'set!))
(define (assignment-variable expr) (second expr))
(define (assignment-value expr) (third expr))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 5. THUNKS, LAMBDAS, PROCEDURES AND APPLICATIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (thunk? obj) (tagged-list? obj 'thunk))
(define (thunk-exp my-thunk) (mcar (mcdr my-thunk)))
(define (thunk-env my-thunk) (mcar (mcdr (mcdr my-thunk))))
(define (delay-it expr env)
(mlist 'thunk expr env))
(define (evaluated-thunk? obj) (tagged-list? obj 'evaluated-thunk))
(define (thunk-value evaluated-thunk) (mcar (mcdr evaluated-thunk)))
(define (force-it obj)
(cond [(thunk? obj)
(define result (actual-value (thunk-exp obj) (thunk-env obj)))
(set-mcar! obj 'evaluated-thunk)
(set-mcar! (mcdr obj) result)
(set-mcdr! (mcdr obj) empty)
result]
[(evaluated-thunk? obj) (thunk-value obj)]
[else obj]))
;; Lambda expressions have the form:
;; (lambda (<parameters>) <body>)
(define (lambda? expr) (tagged-list? expr 'lambda))
(define (lambda-parameters expr) (second expr))
(define (lambda-body expr) (drop expr 2))
(define (make-lambda parameters body)
(cons 'lambda (cons parameters body)))
;; Procedures:
(define (make-procedure parameters body env)
(list 'procedure parameters body env))
(define (compound-procedure? p)
(tagged-list? p 'procedure))
(define (procedure-parameters p) (second p))
(define (procedure-body p) (third p))
(define (procedure-environment p) (fourth p))
;; Procedure applications have the from:
;; (<var> <parameter> ...)
(define (application? expr) (pair? expr))
(define (operator expr) (first expr))
(define (operands expr) (rest expr))
(define (no-operands? ops) (empty? ops))
(define (first-operand ops) (first ops))
(define (rest-operands ops) (rest ops))
(define (actual-value expr env)
(force-it (my-eval expr env)))
(define (list-of-arg-values exprs env)
(if (no-operands? exprs)
empty
(cons (actual-value (first-operand exprs) env)
(list-of-arg-values (rest-operands exprs)
env))))
(define (list-of-delayed-args exprs env)
(if (no-operands? exprs)
empty
(cons (delay-it (first-operand exprs) env)
(list-of-delayed-args (rest-operands exprs)
env))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 6. SEQUENCES AND BEGIN EXPRESSIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Begin has the form: (begin <actions>)
(define (begin? expr) (tagged-list? expr 'begin))
(define (begin-actions expr) (rest expr))
(define (last-exp? seq) (empty? (rest seq)))
(define (first-exp seq) (first seq))
(define (rest-exps seq) (rest seq))
(define (sequence->exp seq)
(cond [(empty? seq) seq]
[(last-exp? seq) (first-exp seq)]
[else (make-begin seq)]))
;; begin constructor used by cond->if
(define (make-begin seq) (cons 'begin seq))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 7. BOOLEAN EXPRESSIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (true? x) (not (false? x)))
(define (and? expr) (tagged-list? expr 'and))
(define (and-operands expr) (rest expr))
(define (make-and sequence) (cons 'and sequence))
(define (or? expr) (tagged-list? expr 'or))
(define (or-operands expr) (rest expr))
(define (make-or sequence) (cons 'or sequence))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 8. IF AND COND EXPRESSIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Conditionals have the form: (if <predicate> <consequent> <alternative>)
;; If no alternative, use false.
(define (if? expr) (tagged-list? expr 'if))
(define (if-predicate expr) (second expr))
(define (if-consequent expr) (third expr))
(define (if-alternative expr)
(if (not (empty? (drop expr 3)))
(fourth expr)
'false))
(define (make-if predicate consequent alternative)
(list 'if predicate consequent alternative))
;; Cond has the form:
;; (cond ((<predicate> <actions>)
;; (else <actions>))) ; if no else, assume (else false) clause
(define (cond? expr) (tagged-list? expr 'cond))
(define (cond-clauses expr) (rest expr))
;; regular cond clause
(define (cond-predicate clause) (first clause))
(define (cond-actions clause) (rest clause))
;; alternate test clause
(define (cond-alternate-clause? clause)
(eq? (second clause) '=>))
(define (cond-test clause) (first clause))
(define (cond-recipient clause) (third clause))
;; else clause
(define (cond-else-clause? clause)
(eq? (cond-predicate clause) 'else))
;; derive cond from if
(define (cond->if expr)
(expand-clauses (cond-clauses expr)))
(define (expand-clauses clauses)
(cond [(empty? clauses) 'false] ; no else clause
[else
(define first-clause (first clauses))
(define rest-clauses (rest clauses))
(cond [(cond-else-clause? first-clause)
(if (empty? rest-clauses)
(sequence->exp (cond-actions first-clause))
(error "ELSE clause isn't last -- COND->IF" clauses))]
[(cond-alternate-clause? first-clause)
(define test (cond-test first-clause)) ; gets evaluated twice
(make-if test
(list (cond-recipient first-clause)
test)
(expand-clauses rest-clauses))]
[else
(make-if (cond-predicate first-clause)
(sequence->exp (cond-actions first-clause))
(expand-clauses rest-clauses))])]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 9. LET, LET*, AND NAMED-LET ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Let has the form: (let (<bindings>) <body>)
(define (let? expr)
(and (tagged-list? expr 'let)
(list? (second expr))))
(define (let-bindings expr) (second expr))
(define (let-parameters expr) (map first (let-bindings expr)))
(define (let-expressions expr) (map second (let-bindings expr)))
(define (let-body expr) (drop expr 2))
(define (let->combination expr)
(cond [(empty? (let-bindings expr)) ; do not unnecessarily lambda wrap
(sequence->exp (let-body expr))]
[else
(cons (make-lambda (let-parameters expr)
(let-body expr))
(let-expressions expr))]))
(define (make-let bindings body)
(cons 'let (cons bindings body)))
;; Let* has the same form as let, but bindings are sequential.
(define (let*? expr) (tagged-list? expr 'let*))
(define (let*-bindings expr) (second expr))
(define (let*-body expr) (drop expr 2))
(define (make-let* bindings body)
(cons 'let* (cons bindings body)))
(define (let*->nested-lets expr)
(define bindings (let*-bindings expr))
(cond [(empty? bindings)
(sequence->exp (let-body expr))]
[else
(list 'let
(list (first bindings))
(let*->nested-lets
(make-let* (rest bindings)
(let-body expr))))]))
;; Named-let has the form: (let <name> (<bindings>) <body>)
(define (named-let? expr)
(and (tagged-list? expr 'let)
(not (list? (second expr)))))
(define (named-let-name expr) (second expr))
(define (named-let-bindings expr) (third expr))
(define (named-let-parameters expr) (map first (named-let-bindings expr)))
(define (named-let-expressions expr) (map second (named-let-bindings expr)))
(define (named-let-body expr) (drop expr 3))
(define (named-let->sequence expr)
(define bindings (named-let-bindings expr))
(cond [(empty? bindings)
(sequence->exp (named-let-body expr))]
[else
(list 'begin
(cons 'define ; first define the named function
(cons (cons (named-let-name expr)
(named-let-parameters expr))
(named-let-body expr)))
(cons (named-let-name expr) ; then apply it to the expressions
(named-let-expressions expr)))]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 10. ENVIRONMENT AND FRAMES ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; An environment is a mutable list of frames. The enclosing environment is the mcdr
;; of the list. A frame is a mutable list of bindings with a 'frame header. A
;; binding is a var-val pair ie (mcons var val).
;; Environments support four procedures:
;; lookup-variable-value
;; extend-environment
;; define-variable
;; set-variable-value
;; Two helper functions support the environment procedures:
;; find-binding-in-frame
;; find-binding-in-environment
(define (enclosing-environment env) (mcdr env))
(define (first-frame env) (mcar env))
(define the-empty-environment empty)
(define the-empty-frame (mlist 'frame))
(define (empty-frame? frame)
(empty? (frame-bindings frame)))
(define (make-frame vars vals)
(mcons 'frame
(mmap mcons
(list->mlist vars)
(list->mlist vals))))
(define (frame-bindings frame) (mcdr frame))
(define (frame-variables frame) (mmap mcar (frame-bindings frame)))
(define (frame-values frame) (mmap mcdr (frame-bindings frame)))
(define (binding-variable binding) (mcar binding))
(define (binding-value binding) (mcdr binding))
(define (set-value! binding val) (set-mcdr! binding val))
(define (add-binding-to-frame! var val frame)
(mappend! frame (mlist (mcons var val))))
(define (find-binding-in-frame var frame)
; Return the var-val pair if present else false.
(define (loop bindings)
(cond [(empty? bindings) false]
[else
(define b (mcar bindings))
(if (eq? var (binding-variable b))
b
(loop (mcdr bindings)))]))
(loop (frame-bindings frame)))
(define (find-binding-in-env var env)
; Return the closest binding for var if present else false.
(cond [(eq? env the-empty-environment) false]
[else
(define b (find-binding-in-frame var (first-frame env)))
(or b (find-binding-in-env var (enclosing-environment env)))]))
(define (lookup-variable-value var env)
(define b (find-binding-in-env var env))
(if b
(binding-value b)
(error "Unbound variable" var)))
(define (extend-environment vars vals base-env)
(cond [(= (length vars) (length vals))
(mcons (make-frame vars vals) base-env)]
[else
(if (< (length vars) (length vals))
(error "Too many arguments supplied" vars vals)
(error "Too few arguments supplied" vars vals))]))
(define (define-variable! var val env)
(define frame (first-frame env))
(define b (find-binding-in-frame var frame))
(if b
(set-value! b val)
(add-binding-to-frame! var val frame)))
(define (set-variable-value! var val env)
(define b (find-binding-in-env var env))
(if b
(set-value! b val)
(error "Unbound variable -- SET!" var)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 11. PRIMITIVE PROCEDURES AND THE GLOBAL ENVIRONMENT ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define primitive-procedures
(list
(list 'cons cons)
(list 'car car)
(list 'cdr cdr)
(list '+ +)
(list '* *)
(list '- -)
(list '< <)
(list '> >)
(list '<= <=)
(list '>= >=)
(list '= =)
(list 'not not)
(list 'false? false?)
(list 'true? (lambda (x) (not (false? x))))
(list 'empty? empty?)
(list 'equal? equal?)
(list 'displayln displayln)
(list 'list list)
))
(define (primitive-procedure? proc)
(tagged-list? proc 'primitive))
(define (primitive-implementation proc)
(second proc))
(define (primitive-procedure-names)
(map first primitive-procedures))
(define (primitive-procedure-objects)
(map (lambda (proc) (list 'primitive (second proc)))
primitive-procedures))
(define apply-in-underlying-scheme apply)
;; The metacircular evaluator's apply is my-apply.
(define (apply-primitive-procedure proc args)
(apply-in-underlying-scheme
(primitive-implementation proc) args))
(define (setup-environment)
(define initial-env
(extend-environment (primitive-procedure-names)
(primitive-procedure-objects)
the-empty-environment))
(define-variable! 'true true initial-env)
(define-variable! 'false false initial-env)
initial-env)
(define the-global-environment (setup-environment))
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 12. REPL OPERATIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;
(define input-prompt ";;; L-Eval input:")
(define output-prompt ";;; L-Eval value:")
(define (driver-loop)
(prompt-for-input input-prompt)
(let* ([input (read)]
[output (actual-value input the-global-environment)])
(announce-output output-prompt)
(user-print output))
(driver-loop))
(define prompt-for-input displayln)
(define announce-output displayln)
(define (user-print object)
(cond [(lazy-list? object)
(displayln (lazy-list->list object))]
[(compound-procedure? object)
(displayln (list 'compound-procedure
(procedure-parameters object)
(procedure-body object)
'<procedure-env>))]
[else (displayln object)]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 13. LAZY LIST OPERATIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (lazy-list? obj)
(and (compound-procedure? obj)
(eq? (first (procedure-parameters obj))
'i-am-a-lazy-list)))
(define (lazy-list-my-car lzl)
(force-it (lookup-variable-value 'x (procedure-environment lzl))))
(define (lazy-list-my-cdr lzl) ; never display this
(force-it (lookup-variable-value 'y (procedure-environment lzl))))
(define (lazy-list->list lzl [num 10]) ; do not accumulate more than num elements
(define (loop acc rst n)
(cond [(zero? n) (reverse (cons '... acc))]
[(empty? rst) (reverse acc)]
[(self-evaluating? rst)
(reverse (cons rst (cons "." acc)))]
[else
(loop (cons (lazy-list-my-car rst) acc)
(lazy-list-my-cdr rst)
(sub1 n))]))
(loop empty lzl num))
(define (list->lazy-list xs)
(if (empty? xs)
empty
(list 'my-cons
(first xs)
(list->lazy-list (rest xs)))))
;; Add these definitions to the global environment:
(define lazy-list-definitions
(list
'(define (my-cons x y)
(lambda (i-am-a-lazy-list)
(i-am-a-lazy-list x y)))
'(define (my-car z)
(z (lambda (p q) p)))
'(define (my-cdr z)
(z (lambda (p q) q)))
'(define (my-list-ref items n)
(if (= n 0)
(my-car items)
(my-list-ref (my-cdr items) (- n 1))))
'(define (my-map proc items)
(if (empty? items)
empty
(my-cons (proc (my-car items))
(my-map proc (my-cdr items)))))
'(define (scale-list items factor)
(my-map (lambda (x) (* x factor))
items))
'(define (add-lists list1 list2)
(cond [(empty? list1) list2]
[(empty? list2) list1]
[else (my-cons (+ (my-car list1) (my-car list2))
(add-lists (my-cdr list1) (my-cdr list2)))]))
'(define ones (my-cons 1 ones))
'(define integers (my-cons 1 (add-lists ones integers)))
'(define (integral integrand initial-value dt)
(define int
(my-cons initial-value
(add-lists (scale-list integrand dt)
int)))
int)
'(define (solve f y0 dt)
(define y (integral dy y0 dt))
(define dy (my-map f y))
y)
))
(define (install-lazy-list-definitions)
(for ([e lazy-list-definitions])
(my-eval e the-global-environment)))
(install-lazy-list-definitions)
Comments
0 B
|👍
/👎
0 B
|👍
/👎