Paradigmata programování II Přednáška 6: Líné vyhodnocování, proudy a kešované vyhodnocování Vilém Vychodil Katedra informatiky, Univerzita Palackého v Olomouci 22. března 2007 Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 1 / 101
Přísliby a líné vyhodnocování Základní myšlenka místo vyhodnocení daného výrazu pracujeme s příslibem jeho budoucího vyhodnocení příslib = nový typ elementu (element prvního řádu) Co je potřeba k tomu, aby to fungovalo: 1 k dispozici je spec. forma (nejčastěji zvaná delay), která pro daný výraz vrací příslib jeho vyhodnocení 2 k dispozici je procedura (nejčastěji zvaná force), která pro daný příslib aktivuje výpočet a vrátí hodnotu vzniklou vyhodnocením přislíbeného výrazu Líné vyhodnocování: vyhodnocování založené na příslibech někdy se nazývá «call by need» Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 3 / 101
;; priklad zamysleneho pouziti (delay (+ 1 2)) = #<promise> (define p (delay (map - '(1 2 3 4)))) p = #<promise> (promise? p) = #t (force p) = (-1-2 -3-4) Poznámky: delay nemůže být z principu procedura, protože chceme, aby se přislíbený výraz vyhodnotil až při aktivaci pomocí force při líném vyhodnocování dochází k propagaci chyb (chyba se projeví na jiném místě než kde vznikla ) (define p (delay blah)) probehne bez problemu p = #<promise>. (force p) = CHYBA (`blah' not bound) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 5 / 101
pomocí příslibů je možné odložit časově složitý výpočet na později a aktivovat jej, až je skutečně potřeba jej provést ;; modelovy casove narocny vypocet: (define fib (lambda (n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2)))))) ;; priklad pouziti (define p (delay (fib 30))). (force p) probehne okamzite aktivace vypoctu (prodleva) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 7 / 101
Při použití příslibů vyvstávají otázky spojené s vedlejším efektem. ;; prislib vyrazu, ktery ma vedlejsi efekt (define p (let ((i 0)) (delay (begin (set! i (+ i 1)) i)))) ;; dvoji aktivace vypoctu (force p) = 1 (force p) =??? Možnosti: 1 druhá aktivace (force p) vrací 1 2 druhá aktivace (force p) vrací 2, třetí vrací 3,... Ukážeme, jak implementovat líné vyhodnocování umožňující obě varianty. Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 9 / 101
Implementace příslibů a líného vyhodnocování přísliby lze plně implementovat pomocí procedur vyšších řádů, maker a vedlejších efektů Základní myšlenka: při vytváření procedur (vyhodnocováním λ-výrazů) nedochází k vyhodnocování těla nově vznikajících procedur k vyhodnocování těla procedur dochází až při jejich aplikaci nabízí se tedy: vytvořit přísliby pomocí procedur ;; vysvetlujici priklad (lambda () (+ 1 2)) (define p (lambda () (+ 1 2))) (p) #<procedure> nas prislib aktivace Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 11 / 101
Implementace příslibů a líného vyhodnocování jednodušší verze při každé aktivaci příslibu je přislíbený výraz vždy vyhodnocen vytvoříme makro freeze ( zmraz ) a proceduru thaw ( roztaj ) ;; specialni forma ``freeze'' (define-macro freeze (lambda exprs `(lambda () (begin,@exprs)))) ;; procedura ``thaw'' (define thaw (lambda (promise) (promise))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 13 / 101
;; priklad vytvoreni prislibu (define p (let ((x 10)) (freeze (display "Hodnota: ") (display x) (newline) (set! x (+ x 1)) (list x (* x x))))) ;; prikladu aktivace prislibu (thaw p) = (11 121) (thaw p) = (12 144) (thaw p) = (13 169) ;; prisliby lze samozrejme pouzivat jako soucast ;; slozitejsich vyrazu: (reverse (thaw p)) = (225 14) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 15 / 101
Implementace příslibů a líného vyhodnocování složitější verze při první aktivaci příslibu je výsledek vyhodnocení přislíbeného výraz zapamatován (uvnitř příslibu) a při každé další aktivaci příslibu je vrácena zapamatovaná hodnota případné vedlejší efekty se projeví jen při první aktivaci vytvoříme makro delay a proceduru force ;; nejdriv priklad pouziti: (define p (let ((x 10)) (delay (set! x (+ x 1)) (list x (* x x))))) (force p) = (11 121) (force p) = (11 121) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 17 / 101
Implementace příslibů a líného vyhodnocování ;; specialni forma ``delay'' (define-macro delay (lambda exprs `(let ((result (lambda () (begin,@exprs))) (evaluated? #f)) (lambda () (begin (if (not evaluated?) (begin (set! evaluated? #t) (set! result (result)))) result))))) ;; procedura ``force'' (totez co ``thaw'') (define force thaw) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 19 / 101
Proudy (angl. Streams) proudy jsou nejčastěji používanou aplikací líného vyhodnocování neformálně: proudy jsou líně vyhodnocované seznamy konstruktor cons-stream a selektory stream-car a stream-cdr ;; konstruktor proudu (je makro!) (define-macro cons-stream (lambda (a b) `(cons,a (delay,b)))) ;; selektor stream-car (vrat prvni prvek proudu) (define stream-car car) ;; selektor stream-cdr (vrat proud bez prvniho prvku) (define stream-cdr (lambda (stream) (force (cdr stream)))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 21 / 101
Definice proudů prázdný seznam je proud; každý tečkový pár (e. p), kde e je libovolný element a p je příslib proudu, je proud. ;; je stream prazdny? (define stream-null? null?) ;; predikat stream? (podle definice) (define stream? (lambda (elem) (or (null? elem) (and (pair? elem) (and (promise? (cdr elem)) (stream? (force (cdr elem)))))))) předchozí predikát stream? má nevýhodu: používá force (!) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 23 / 101
;; slabsi verze predikatu stream? ;; pazdy par, jehoz 2. prvek je prislib nebo () je stream (define stream? (lambda (elem) (or (null? elem) (and (pair? elem) (or (promise? (cdr elem)) (null? (cdr elem))))))) Pomocné procedury: ;; zobraz stream, nanejvys vsak n prvnich prvku (define display-stream (lambda (stream. n) ;; odvozene selektory (define stream-caar (lambda (x). (define stream-cddddr (lambda (x) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 25 / 101
Procedury pro práci s proudy ;; delka streamu (define stream-length (lambda (stream) (if (stream-null? stream) 0 (+ 1 (stream-length (stream-cdr stream)))))) ;; mapovani pres streamy (mapovani pres jeden stream) (define stream-map2 (lambda (f stream) (if (stream-null? stream) '() (cons-stream (f (stream-car stream)) (stream-map f (stream-cdr stream)))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 27 / 101
Procedury pro práci s proudy ;; mapovani pres streamy (obecna verze) (define stream-map (lambda (f. streams) (if (stream-null? (car streams)) '() (cons-stream (apply f (map stream-car streams)) (apply stream-map f (map stream-cdr streams)))))) ;; konvertuj seznam na stream (define list->stream (lambda (list) (foldr (lambda (x y) (cons-stream x y)) '() list))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 29 / 101
Procedury pro práci s proudy ;; vytvor konecny stream danych prvku (define stream (lambda args (list->stream args))) Příklady použití předchozích procedur: (define s (stream 1 2 3 4)) s = (1. #<promise>) (display-stream s) = #<stream (1 2 3 4)> (display-stream s 2) = #<stream (1 2)> (stream-length s) = 4 (display-stream (stream-map - s) 2) = #<stream (-1-2)> (display-stream (stream-map + s s s)) = #<stream (3 6 9 12)> Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 31 / 101
Všimněte si při práci s proudy mají jednotlivé procedury jinou odezvu výpočet je řízen daty, dochází k propagaci chyb ;; vysledek je vracen okamzite (define fs (stream-map fib (stream 1 30 31 50))) fs = (1. #<promise>) ;; pristup ke dalsim prvkum se bude postupne zpomalovat ;; ukazka propagace chyb v proudech (define s (stream 1 2 3 4 5 'blah 6 7)) (define r (stream-map - s)) r = (-1. #<promise>) (display-stream r 4) = (-1-2 -3-4) (display-stream r 6) = (-1-2 -3-4 -5 CHYBA Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 33 / 101
Úskalí ;; zdanlive funkcni verze `foldr' pro streamy (define stream-foldr (lambda (f nil. streams) (if (stream-null? (car streams)) nil (apply f `(,@(map stream-car streams),(apply stream-foldr f nil (map stream-cdr streams))))))) ;; nasledujici se nechova prirozene (stream-foldr (lambda (x y) (cons-stream (- x) y)) '() (stream 1 2 3 'blah 4)) = CHYBA: nelze aplikovat `-' na symbol `blah' Čekali bychom, že chyba se projeví až při pokusu přistoupit ke 4. prvku výsledného proudu Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 35 / 101
Nová verze stream-foldr proceduře f bude předáván místo druhého argumentu jeho příslib procedura sama rozhodne, jak bude s příslibem nakládat ;; procedura stream-folder (define stream-foldr (lambda (f nil. streams) (if (stream-null? (car streams)) nil (apply f `(,@(map stream-car streams),(delay (apply stream-foldr f nil (map stream-cdr streams)))))))) (stream-foldr (lambda (x y) (cons-stream (- x) (force y))) '() (stream 1 2 3 'blah 4)) = (-1. #<promise>) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 37 / 101
Další (užitečné) odvozené procedury ;; konverze streamu na seznam (define stream->list (lambda (stream) (stream-foldr (lambda (x y) (cons x (force y))) '() stream))) ;; filtrace prvku streamu podle vlastnosti (define stream-filter (lambda (prop? stream) (stream-foldr (lambda (x y) (if (prop? x) (cons-stream x (force y)) (force y))) '() stream))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 39 / 101
Nekonečné proudy a jejich implicitní definice Příklad (proud jedniček) ;; rekurzivni procedura,,bez limitni podminky'' (define ones-proc (lambda () (cons-stream 1 (ones-proc)))) ;; nekonecny proud vytvoreny volanim ones-proc (define ones (ones-proc)) ;; predchozi s pouzitim pojmenovaneho ``let'' (define ones (let proc () (cons-stream 1 (proc)))) ;; implicitni definice proudu (define ones (cons-stream 1 ones)) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 41 / 101
Nekonečné proudy a jejich implicitní definice Příklad (proud přirozených čísel) ;; rekurzivni procedura,,bez limitni podminky'' (define naturals-proc (lambda (i) (cons-stream i (naturals-proc (+ i 1))))) ;; nekonecny proud vytvoreny volanim ones-proc (define naturals (naturals-proc 1)) ;; predchozi s pouzitim pojmenovaneho ``let'' (define naturals (let iter ((i 1)) (cons-stream i (iter (+ i 1))))) ;; implicitni definice proudu (define naturals (cons-stream 1 (stream-map + ones naturals))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 43 / 101
Nekonečné proudy Nekonečný proud neformálně: potenciálně nekonečná lineární datová struktura potenciálně nekonečná znamená: opakovaným použitím stream-cdr se nedostaneme na jejich konec v každém okamžiku průchodu nekonečným proudem máme vždy k dispozici aktuální prvek a příslib pokračování proudu lze se na něj dívat jako na nekonečnou posloupnost elementů (e i ) i=0, to jest e 0, e 1, e 2,..., e n 1, e n, e n+1,... v praxi se konstruuje rekurzivní procedurou bez limitní podmínky ;; proud hodnot (2 i ) i=0 (define pow2 (let next ((last 1)) (cons-stream last (next (* 2 last))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 45 / 101
Nekonečné proudy Formálně lze zavést jako limity prefixových generátorů Seznam r je prefix seznamu t, pokud lze t vyjádřit jako spojení r s nějakým seznamem l (v tomto pořadí). Množinu seznamů S nazveme prefixový generátor, pokud 1 pro každé n N, systém S obsahuje seznam délky n; 2 pro každé dva s, t S platí: buď s je prefix t, nebo t je prefix s. Nekonečný proud (příslušný prefixovému generátoru S) je element reprezentující posloupnost (e i ) i=0, kde e i je element nacházející se na i-té pozici libovolného seznamu s S majícího alespoň i + 1 prvků. Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 47 / 101
Implicitní definice nekonečného proudu definice proudu, která v sobě používá proud, který sama definuje následující prvky proudu jsou přímo zavedeny pomocí předchozích prvků proudu bez vytváření pomocné rekurzivní procedury (bez limitní podmínky) ;; nekonecny proud (define pow2 (let next ((last 1)) (cons-stream last (next (* 2 last ))))) ;; implicitni definice predchoziho (define pow2 (cons-stream 1 (stream-map (lambda (x) (* 2 x)) pow2))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 49 / 101
Ukázky implicitních definic proudů ;; proud faktorialu (define fak-stream (cons-stream 1 (stream-map * fak-stream (stream-cdr naturals)))) ;; proud Fibonacciho cisel (define fib-stream (cons-stream 1 (cons-stream 1 (stream-map + fib-stream (stream-cdr fib-stream))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 51 / 101
Konstruktor nekonečných proudů nekonečné proudy lze vytvářet procedurou build-stream analogická proceduře build-list, ale nemá limitní podmínku (není potřeba předávat délku vytvářeného streamu ) ;; konstruktor `build-stream' (define build-stream (lambda (f) (let proc ((i 0)) (cons-stream (f i) (proc (+ i 1)))))) ;; priklady (define ones (build-stream (lambda (i) 1))) (define naturals (build-stream (lambda (i) (+ i 1)))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 53 / 101
Příklady manipulace s nekonečnými proudy ;; vytvareni nekonecnych proudu z nekonecnych proudu ;; aplikaci po sobe jdoucich funkci na kazdy prvek (define expand-stream (lambda (stream. modifiers) (let next ((pending modifiers)) (if (null? pending) (apply expand-stream (stream-cdr stream) modifiers) (cons-stream ((car pending) (stream-car stream)) (next (cdr pending))))))) ;; priklady pouziti: (expand-stream ones - +) = proud: -1 1-1 1 (expand-stream ones + -) = proud: 1-1 1-1 (expand-stream naturals + -) = proud: 1-1 2-2 3 Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 55 / 101
Příklady manipulace s nekonečnými proudy ;; vytvoreni proudu celych cisel (define integers (build-stream (lambda (i) (if (= i 0) 0 ((if (even? i) + -) (quotient (+ i 1) 2)))))) ;; nebo pouzitim streamu prirozenych a expand-stream (define integers (cons-stream 0 (expand-stream naturals - +))) ;; v obou pripadech dostavame integers = proud: 0-1 1-2 2-3 3-4 4-5 Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 57 / 101
Příklady (nesprávné) manipulace s nekonečnými proudy ;; nasledujici ma smysl pouze pokud je `s1' konecny: (define stream-append2 (lambda (s1 s2) (stream-foldr (lambda (x y) (cons-stream x (force y))) s2 s1))) ;; druhy stream se neuplatni, protoze prvni je nekonecny (stream-append ones (stream-map - ones)) ;; nasledujici bude cyklit (stream-length ones) počítat délku (nekončeného) streamu je nesmysl neexistuje algoritmus (a nikdy existovat nebude), který by pro daný stream rozhodl, zda-li je konečný či nikoliv Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 59 / 101
Proud racionálních čísel můžeme vytvořit i proud všech racionálních čísel využijeme faktu, že všechna kladná racionální čísla jsou zapsána v následující tabulce (každé dokonce nekonečně mnoho krát) a toho, že tabulku můžeme projít po položkách v diagonálním směru POZNÁMKA: proud všech reálných čísel nelze vytvořit (!) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 61 / 101
Proud racionálních čísel ;; stream paru (1. 1) (2. 1) (1. 2) (3. 1) (define pairs (let next ((sum 2) (a 1) (b 1)) (cons-stream (cons a b) (if (= a 1) (next (+ sum 1) sum 1) (next sum (- a 1) (+ b 1)))))) ;; stream zlomku 1/1 2/1 1/2 3/1 2/2 1/3 (define fractions (stream-map (lambda (x) (/ (car x) (cdr x))) pairs)) kladná racionální čísla: zbývá odstranit opakující se čísla z fractions (například 1/1 je totéž jako 2/2, a podobně) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 63 / 101
Proud racionálních čísel ;; proud kladnych racionalnich cisel ;; stejna cisla jsou odstranena filtraci (define positive-rationals (let next ((f-stream fractions)) (cons-stream (stream-car f-stream) (next (stream-filter (lambda (x) (not (= x (stream-car f-stream)))) f-stream))))) ;; a konecne: stream vsech racionalnich cisel ;; 0-1 1-2 2-1/2 1/2-3 3-1/3 1/3-4 4-3/2 3/2 (define rationals (cons-stream 0 (expand-stream positive-rationals - +))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 65 / 101
Kdy použít proudy? Kdy použít proudy místo seznamů? 1 když potřebujeme řídit průběh výpočtu pomocí dat 2 když není dopředu známa velikost dat, která chceme zpracovávat, nebo není možné odhadnout, kolik dat budeme muset zpracovat, než najdeme řešení (nějakého) problému Příklad: pokud se budeme pokoušet najít ve velkém souboru sekvenci nějakých znaků odpovídající danému vzoru, pak nemá smysl natahovat celý vstupní soubor do paměti, což může být dlouhá operace (nebo i neproveditelná operace), protože hledaný řetězec může být třeba někde na začátku souboru. Typické použití proudů: řízení vstupně/výstupních operací práce se soubory používá mnoho PJ (například C++) my ukážeme implementaci V/V operací ve Scheme Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 67 / 101
Vstupně/výstupní operace ve Scheme Scheme používá při manipulaci se soubory tzv. porty port lze chápat jako identifikátor otevřeného souboru pro detaily viz specifikaci R5RS ;; priklad ukladani dat do souboru (define p (open-output-file "soubor.txt")) (display "Ahoj svete!" p) (newline p) (display (map - '(1 2 3 4)) p) (newline p) (close-output-port p) ;; priklad nacitani dat ze souboru (define p (open-input-file "soubor.txt")) (display (read p)) (display (read p)) (display (read p)) (close-input-port p) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 69 / 101
Jednoduché vstupní proudy ;; vytvor stream ctenim vyrazu z vstupniho portu (define input-port->stream (lambda (reader port) (let iter () (let ((elem (reader port))) (if (eof-object? elem) (begin (close-input-port port) '()) (cons-stream elem (iter))))))) ;; vytvori stream otevrenim souboru (define file->stream (lambda (reader file-name) (input-port->stream reader (open-input-file file-name)))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 71 / 101
Zvýšení výpočetní efektivity pomocí proudů predikát equal-fringe? je pro dva seznamy pravdivý p. k. oba seznamy mají stejné atomické prvky pokud je projdeme zleva-doprava ;; primocare reseni, ktere je neefektivni (define equal-fringe? (lambda (s1 s2) (define flatten ; pomocna proc.: linearizace seznamu (lambda (l) (cond ((null? l) '()) ((list? (car l)) (append (flatten (car l)) (flatten (cdr l)))) (else (cons (car l) (flatten (cdr l))))))) (equal? (flatten s1) (flatten s2)))) ;; priklad pouziti: (equal-fringe? '(a (b (c)) () d) '(a b c (d))) = #t (equal-fringe? '(a (b (c)) () d) '(a b c (e))) = #f Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 73 / 101
Zvýšení výpočetní efektivity pomocí proudů V čem spočívá neefektivita předchozího řešení? 1 během výpočtu se konstruují lineární seznamy, které se potom použijí pouze jednorázově 2 predikát se nechová přirozeně. Pokud máme dva seznamy ve tvaru (a a (b, pak je okamžitě jasné, že výsledek pro ně by měl být #f, ale předchozí procedura je oba nejprve celé linearizuje Odstraníme problém č. 2: vstupní seznamy budeme linearizovat do proudu (to jest výsledkem linearizace seznamu bude proud atomů) okamžitě budeme mít k dispozici nejlevější atom ostatní prvky linearizovaného seznamu se budou hledat až když přistoupíme k dalšímu prvku proudu vytvoříme predikát na test shody dvou konečných proudů Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 75 / 101
Zvýšení výpočetní efektivity pomocí proudů ;; kazdy prislib je roven pouze sam sobe: (define d (delay 1)) (equal? d d) = #t (equal? (delay 1) (delay 1)) = #f ;; tim padem: (equal? (stream 'a 'b 'c) (stream 'a 'b 'c)) = #f ;; proto zavadime predikat shodnosti dvou streamu: (define stream-equal? (lambda (s1 s2) (or (and (null? s1) (null? s2)) (and (equal? (stream-car s1) (stream-car s2)) (stream-equal? (stream-cdr s1) (stream-cdr s2)))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 77 / 101
Zvýšení výpočetní efektivity pomocí proudů ;; temer dokonala verze equal-fringe? (define equal-fringe? (lambda (s1 s2) ;; pomocna definice: linearizace seznamu (define flatten (lambda (l) (cond ((null? l) '()) ((list? (car l)) (stream-append2 (flatten (car l)) (flatten (cdr l)))) (else (cons-stream (car l) (flatten (cdr l))))))) ;; jsou linearni seznamy totozne? (stream-equal? (flatten s1) (flatten s2)))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 79 / 101
Zvýšení výpočetní efektivity pomocí proudů Předchozí řešení má jeden malý problém ;; priklad na kterem nas predikat selhava (define a '(a)) (set-cdr! a a) (define b '((b))) (set-cdr! b b) (equal-fringe? a b) = (cyklí) ;; ODSTRANENI PROBLEMU (rozmyslete si proc): ;; misto procedury stream-append2 vytvorime makro (define-macro stream-append2 (lambda (s1 s2) `(let proc ((s,s1)) (if (stream-null? s),s2 (cons-stream (stream-car s) (proc (stream-cdr s))))))) nový pohled: makro = procedura s líně vyhodnocenými argumenty Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 81 / 101
Vyhodnocovací proces s vyrovnávací pamětí ;; modelovy program (define fib (lambda (n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2)))))) Výhody a nevýhody předchozího kódu: výhoda: je čistě napsaný (vznikl přepisem definice fib. čísla) nevýhoda: je neefektivní dochází ke zbytečnému opakování výpočtů Otázka: Jak zachovat čitelnost kódu, ale zvýšit efektivitu výpočetního procesu? Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 83 / 101
Vyhodnocovací proces s vyrovnávací pamětí ;; iterativni verze pocedury ;; predstavuje zrychleni na ukor citelnosti kodu (define fib (lambda (n) (let iter ((a 1) (b 1) (n n)) (if (<= n 1) a (iter b (+ a b) (- n 1)))))) Lepší řešení: zachováme původní kód procedury proceduru zabalíme do další procedury (memoize), která bude mít svou vnitřní vyrovnávací paměť do které bude ukládat výsledky aplikace výchozí procedury odstraníme tak problém opakovaného provádění stejných výpočtů Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 85 / 101
Vytvoříme procedury empty-assoc a assoc! pro správu paměti ;; prazdna pamet (define empty-assoc (lambda () (cons (cons #f #f) '()))) ;; destruktivne zarad novy zaznam/modifikuj existujici (define assoc! (lambda (assoc key val) (let iter ((as assoc)) (cond ((null? as) (set-cdr! assoc (cons (cons key val) (cdr assoc)))) ((equal? (caar as) key) (set-cdr! (car as) val)) (else (iter (cdr as))))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 87 / 101
;; priklad zarazeni novych zaznamu: (define a (empty-assoc)) a = ((#f. #f)) (assoc! a 'ahoj 10) a = ((#f. #f) (ahoj. 10)) (assoc! a 'blah 20) a = ((#f. #f) (blah. 20) (ahoj. 10)) (assoc! a 'ahoj 30) a = ((#f. #f) (blah. 20) (ahoj. 30)) ;; vyhledavani lze provest pomoci klasickeho assoc (assoc 'blah a) = (blah. 20) (assoc #f a) = (#f. #f) Poznámka: pár (#f. #f) je vždy přítomen kvůli mutaci Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 89 / 101
Vyhodnocovací proces s vyrovnávací pamětí procedura memoize vytvoří obálku nad danou procedurou (provede memoizaci dané procedury) každá memoizovaná procedura má vlastní paměť pro úschovu výsledků při volání memoizované procedury s již dříve použitými argumenty je výsledná hodnota vyhledána v paměti (define memoize (lambda (f) (let ((memory (empty-assoc))) (lambda called-with-args (let ((found (assoc called-with-args memory))) (if found (cdr found) (let ((result (apply f called-with-args))) (assoc! memory called-with-args result) result))))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 91 / 101
;; fibonacciho cisla urychlena pomoci memoize (define fib (memoize (lambda (n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))))) ;; POZOR: tohle by nefungovalo: (define fib (lambda (n) puvodni pomaly fib (define fast-fib (memoize fib)) (fast-fib 32) bude ve skutecnosti pomaly ve fast-fib se rekurzivně volá původní procedura bez cache při (fast-fib 32) je zapamatován pouze výsledek pro 32 nevede ke kýženému zrychlení výpočtu Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 93 / 101
;; procedury s kesi se chovaji jinak nez,,klasicke'' ;; v pripade pouziti vedlejsiho efektu (let ((cntr 0)) (define modify (lambda (x) (set! cntr (+ 1 cntr)))) (modify #f) (modify #f) cntr) = 2 (let ((cntr 0)) (define modify (memoize (lambda (x) (set! cntr (+ 1 cntr))))) (modify #f) (modify #f) cntr) = 1 Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 95 / 101
;; vylepsena verze: pri preplneni pameti se pamet vysype (define make-memoize (lambda limit (lambda (f) (let ((memory (empty-assoc)) (memory-size 0)) (lambda called-with-args (let ((found (assoc called-with-args memory))) (if found (cdr found) (let ((result (apply f called-with-args))) (if (and (not (null? limit)) (> memory-size (car limit))) (begin (set! memory-size 0) (set! memory (empty-assoc)))) (assoc! memory called-with-args result) (set! memory-size (+ memory-size 1)) result)))))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 97 / 101
;; urychlene fib. s pameti o peti bunkach (define fib ((make-memoize 5) (lambda (n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))))) Srovnání počtu aktivací vnitřní procedury při dané velikosti paměti během výpočtu (fib 32) velikost paměti 50 20 15 10 5 2 1 počet aktivací 32 242 271 1709 6985 75183 287127 Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 99 / 101
Makro pro vytváření procedur s keší ;; globalni,,konstanta'' udavajici velikost pameti (define *max-memory* 1000) ;; makro kappa (define-macro kappa (lambda (args. body) `((make-memoize *max-memory*) (lambda,args,@body)))) ;; priklad pouziti: (define fib (kappa (n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2)))))) Vilém Vychodil (UP Olomouc) PP II, Př. 6: Lazy, Streams, Cache 22. března 2007 101 / 101