Commit 163ab763 authored by James R. Wilcox's avatar James R. Wilcox
Browse files

lec22 code

parent 571a86f5
; loops pointlessly n times before returning x
(define (slow-id n x)
(if (= n 0)
x
......@@ -5,21 +6,46 @@
(slow-id 10 'hello)
; uses slow-id to slow down the addition operation :)
(define (slow-add a b)
(+ (slow-id 10000000 a) b))
(let ((_ (print! 'slow-add)))
(+ (slow-id 10000000 a) b)))
(slow-add 2 3)
;(slow-add 2 3) ; slow, unsurprisingly
(define (my-mult a b-thunk)
(match a
(0 0)
(1 (b-thunk))
(_ (+ (b-thunk) (slow-mult (- a 1) b-thunk)))))
(_ (+ (b-thunk) (my-mult (- a 1) b-thunk)))))
; (my-mult 0 (lambda () (slow-add 3 4))) ; fast! thunk never called
; (my-mult 1 (lambda () (slow-add 3 4))) ; slow! thunk called once
; (my-mult 2 (lambda () (slow-add 3 4))) ; even slower! thunk called twice
;
; (my-mult 0 (let ((n (slow-add 3 4))) (lambda () n))) ; slow! (thunk called once)
; (my-mult 1 (let ((n (slow-add 3 4))) (lambda () n))) ; slow! (thunk called once)
; (my-mult 2 (let ((n (slow-add 3 4))) (lambda () n))) ; slow! (thunk called once) (so not as slow as "even slower"!)
; wanted: a way to evaluate a thunk "at most once", and save the result in case we need it again
(struct promise evaluated? thunk-or-data)
(define (delay thunk)
(make-ref (promise false thunk)))
(define (force p)
(let ((promise-struct (read-ref p)))
(if (promise-evaluated? promise-struct)
(promise-thunk-or-data promise-struct)
(let ((thunk (promise-thunk-or-data (read-ref p))))
(let ((data (thunk)))
(let ((_ (write-ref p (promise true data))))
data))))))
; best of both worlds: if forced zero times, thunk is never called. if forced > once, thunk only called once
; (my-mult 0 (let ((p (delay (lambda () (slow-add 3 4))))) (lambda () (force p)))) ; fast! (thunk called zero times)
; (my-mult 1 (let ((p (delay (lambda () (slow-add 3 4))))) (lambda () (force p)))) ; slow! (thunk called once)
; (my-mult 2 (let ((p (delay (lambda () (slow-add 3 4))))) (lambda () (force p)))) ; slow, but not "even slower"! (thunk called once)
(my-mult 0 (lambda () (slow-add 3 4)))
(my-mult 1 (lambda () (slow-add 3 4)))
(my-mult 2 (lambda () (slow-add 3 4)))
(my-mult 0 (let ((n (slow-add 3 4))) (lambda () n)))
(my-mult 1 (let ((n (slow-add 3 4))) (lambda () n)))
(my-mult 2 (let ((n (slow-add 3 4))) (lambda () n)))
(define x 1)
; streams are "emergent" in the sense that they are built out of other features
; recursive functions
; pairs
; first-class functions
;
;
; python's generators ("yield" keyword)
; similar to java's iterators
; from friday
(define (first-n-elements n stream)
(if (= n 0)
nil
(let ((pair (stream)))
(cons (car pair)
(first-n-elements (- n 1) (cdr pair))))))
(define (all-natural-numbers-starting-at k)
(cons k (lambda () (all-natural-numbers-starting-at (+ k 1)))))
; return the infinite sequence 0, 1, 2, ...
(define (all-natural-numbers)
(all-natural-numbers-starting-at 0))
(first-n-elements 10 all-natural-numbers)
;; end of friday copy-paste
(define (the-stream-of-all-keyboard-characters)
(cons (get-char!) the-stream-of-all-keyboard-characters))
; you could imagine the standard library providing this stream as the primary of reading input
;;;;;;;;;;;;;;;;;;;;;
; PROMISES
;;;;;;;;;;;;;;;;;;;;;
; remember thunks
(define my-thunk (lambda () (print! 'hello)))
(define my-other-thunk (lambda () (let ((x (print! 'hello))) (+ 1 2))))
; in OCaml, we would write
; print_endline "hello";
; 1 + 2
;
; in trefoil, we "implement" the semicolon by hand using a "useless" let
; the "slow identity function"
; loops n times before returning x
(define (slow-id n x)
(if (= n 0)
x
(slow-id (- n 1) x)))
; returns a + b, but after a delay
(define (slow-add a b)
(let ((x (print! 'slow-add-was-called)))
(+ (slow-id 10000000 a) b)))
; multiplication where the second argument is thunked
;
; another way to think about this:
; sums up the result of calling b-thunk "a times"
(define (my-mult a b-thunk)
(match a
(0 0)
(_ (+ (b-thunk) (my-mult (- a 1) b-thunk)))))
; (my-mult 0 (lambda () (slow-add 2 3))) ; fast (thunk is not called)
; (my-mult 1 (lambda () (slow-add 2 3))) ; slow (thunk is called once)
; (my-mult 2 (lambda () (slow-add 2 3))) ; even slower! (thunk is called twice)
; (my-mult 0 (let ((n (slow-add 2 3))) (lambda () n))) ; slow (calls slow-add once, even though thunk is never called) (worse!)
; (my-mult 1 (let ((n (slow-add 2 3))) (lambda () n))) ; slow (the same as before)
; (my-mult 2 (let ((n (slow-add 2 3))) (lambda () n))) ; slow (but not as slow as before, because slow-add called only once)
; (my-mult 10 (let ((n (slow-add 2 3))) (lambda () n))) ; slow (but much faster than before)
; promises are a way to get the best of both worlds
; fast if the result is never used
; they will only compute the answer once, even if it is used multiple times
; design a data structure that wraps a delayed computation (ie, it wraps a thunk)
; - allows clients to ask for the result of the computation
; - if this is the first time anyone has asked for the result, compute it (by calling the thunk), and save it
; - if this is the second or later time, return previously saved answer
; fundamentally requires mutation
;
; -> introduce refs to trefoil
; in OCaml:
; - t ref is the type of references that point to values of type t
; - build a value:
; - ref e
; - use these values
; - reading them via !e
; - writing them via e1 := e2
; add three expression to trefoil
; - make a reference
; - (make-ref! e)
; - read a reference
; - (read-ref e)
; - write a reference
; - (write-ref! e1 e2)
; promise data structure stores
; - a boolean saying whether or not we have already evaluated the thunk
; - if we have already, then we store the result of the computation
; - if we have no, then we store the thunk
; if evaluated? then thunk-or-data stores the result, otherwise it stores the thunk
(struct promise-fields evaluated? thunk-or-data)
; a promise is a reference to a promise-fields struct
; traditionally, this operation is called "delay", but that's confusing with thunks
; maybe a better name would be promise-delay
(define (make-promise thunk)
(make-ref! (promise-fields false thunk)))
; takes a promise and returns its value
; - might involve calling thunk if it hasn't been called yet!
; - or it might not, if the thunk was already called
(define (force-promise p)
(if (promise-fields-evaluated? (read-ref p))
(promise-fields-thunk-or-data (read-ref p))
(let ((thunk (promise-fields-thunk-or-data (read-ref p))))
(let ((data (thunk)))
(let ((x (write-ref! p (promise-fields true data))))
data)))))
(define p (make-promise (lambda () (slow-add 2 3))))
; (force-promise p)
; (force-promise p)
(define (my-mult-promise a b-promise)
(match a
(0 0)
(_ (+ (force-promise b-promise) (my-mult-promise (- a 1) b-promise)))))
; best of both worlds!
; (my-mult-promise 0 (make-promise (lambda () (slow-add 2 3)))) ; fast because promise is never forced
; (my-mult-promise 1 (make-promise (lambda () (slow-add 2 3)))) ; slow
; (my-mult-promise 2 (make-promise (lambda () (slow-add 2 3)))) ; slow, but only as slow as once
; (my-mult-promise 10 (make-promise (lambda () (slow-add 2 3)))) ; slow, but only as slow as once
; how often are promises used?
; there are some languages where every expression is automatically wrapped in a promise
; these are called "lazy" languages
; famous example: Haskell
; there's a form of caching called "memoization"
; it's a function that
; - when called a "new input", actually does the computation, and then stores the result in a table
; - when called on an "old input", looks up in the table
;
; especially interesting if function is recursive
; especially especially interesting for dynamic programming
; memoization
; macros
(define (transform-syntax syn)
(match syn
((cons '+ (cons a (cons b (cons c nil))))
(cons '+ (cons a (cons (cons '+ (cons b (cons c nil))) nil))))))
(define foo (quote (+ 1 2 3)))
(transform-syntax foo)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment