Paradigmata programování II Přednáška 6: Líné vyhodnocování, proudy a kešované vyhodnocování

Podobné dokumenty
PARADIGMATA PROGRAMOVÁNÍ 2 PŘÍSLIBY A LÍNÉ VYHODNOCOVÁNÍ

Paradigmata programování 1

PARADIGMATA PROGRAMOVÁNÍ 2A INTERPRET S VEDLEJŠÍMI EFEKTY A MAKRY

Paradigmata programování II Přednáška 2: Mutace

Paradigmata programování 1

Paradigmata programování II Přednáška 1: Vedlejší efekt

Paradigmata programování 1

Paradigmata programování II Korutiny a nedeterminismus

PARADIGMATA PROGRAMOVÁNÍ 2A VEDLEJŠÍ EFEKT

PARADIGMATA PROGRAMOVÁNÍ 2A MUTACE

Paradigmata programování 2

1. Od Scheme k Lispu

PARADIGMATA PROGRAMOVÁNÍ 2 KORUTINY, NEDETERMINISMUS

PARADIGMATA PROGRAMOVÁNÍ 2 AKTUÁLNÍ POKRAƒOVÁNÍ

PARADIGMATA PROGRAMOVÁNÍ 2A MAKRA III

Přednáška 3. Rekurze 1

Slepé prohledávání do šířky Algoritmus prohledávání do šířky Při tomto způsobu prohledávání máme jistotu, že vždy nalezneme koncový stav, musíme ale p

FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 3. CVIČENÍ

PARADIGMATA PROGRAMOVÁNÍ 2A MAKRA I

Funkcionální programování úvod

Paradigmata programování 1

(pracovní verze textu určená pro studenty)

Základní datové struktury

Lokální definice (1) plocha-kruhu

Hanojská věž. T2: prohledávání stavového prostoru. zadání [1 1 1] řešení [3 3 3] dva možné první tahy: [1 1 2] [1 1 3]

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Jazyk C# (seminář 5)

FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 5. CVIČENÍ

9 - Map/filter/reduce OMO. Ing. David Kadleček, PhD

SII - Informatika. 1. Atribut relace, jehož hodnota jednoznačně určuje prvek v jiné relaci, se nazývá:

VISUAL BASIC. Práce se soubory

Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:

Stream API. Petr Krajča. Základy programovaní 4 (Java) Katedra informatiky Univerzita Palackého v Olomouci

Test prvočíselnosti. Úkol: otestovat dané číslo N, zda je prvočíslem

Reprezentace aritmetického výrazu - binární strom reprezentující aritmetický výraz

Algoritmizace Dynamické programování. Jiří Vyskočil, Marko Genyg-Berezovskyj 2010

Binární soubory (datové, typované)

LISP Definice funkcí

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

Maturitní otázky z předmětu PROGRAMOVÁNÍ

Lineární spojový seznam (úvod do dynamických datových struktur)

O datových typech a jejich kontrole

Práce se soubory. Úvod do programování 2 Tomáš Kühr

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu.

Práce s binárními soubory. Základy programování 2 Tomáš Kühr

Programování v jazyce C pro chemiky (C2160) 3. Příkaz switch, příkaz cyklu for, operátory ++ a --, pole

Obsah přednášky. programovacího jazyka. Motivace. Princip denotační sémantiky Sémantické funkce Výrazy Příkazy Vstup a výstup Kontinuace Program

Časová a prostorová složitost algoritmů

Algoritmizace a programování

Logické programování

Funkce pokročilé možnosti. Úvod do programování 2 Tomáš Kühr

Rekurze. doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava. Prezentace ke dni 12.

Práce se soubory. Základy programování 2 Tomáš Kühr

Sdílení dat mezi podprogramy

Jazyk Scheme: jeho syntax a sémantika

Programování: základní konstrukce, příklady, aplikace. IB111 Programování a algoritmizace

Aplikace. vliv na to, jakou mají strukturu i na to, jak pracné je je vyvinout. Bylo vypozorováno, že aplikace je možné rozdělit do skupin

Definice uživatelského typu. Uživatelem definované typy. Součinové datové typy. Součtové datové typy. FLP - Uživatelem definované typy

Rekurzivní algoritmy

Prohledávání do šířky = algoritmus vlny

8. Rekurze. doc. Ing. Jiří Vokřínek, Ph.D. Katedra počítačů Fakulta elektrotechnická České vysoké učení technické v Praze

2.1 Podmínka typu case Cykly Cyklus s podmínkou na začátku Cyklus s podmínkou na konci... 5

Programování v Pythonu

Programování v jazyce C a C++

Odvozené a strukturované typy dat

IB015 Neimperativní programování. Časová složitost, Typové třídy, Moduly. Jiří Barnat Libor Škarvada

Programování v Pythonu

Seznamy a iterátory. Kolekce obecně. Rozhraní kolekce. Procházení kolekcí

Lineární datové struktury

Implementace LL(1) překladů

Vyhledávání. doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava. Prezentace ke dni 21.

map, multimap - Asociativní pole v C++.

Spojová implementace lineárních datových struktur

Programování v Pythonu

Operátory ROLLUP a CUBE

Paradigmata programování 1 poznámky k přednášce. 3. Rekurze 1

Struktura programu v době běhu

Databázové systémy. * relační kalkuly. Tomáš Skopal. - relační model

Assembler - 5.část. poslední změna této stránky: Zpět

Základní způsoby: -Statické (přidělění paměti v čase překladu) -Dynamické (přiděleno v run time) v zásobníku na haldě

Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

Programovací jazyk Haskell

Přednáška. Správa paměti II. Katedra počítačových systémů FIT, České vysoké učení technické v Praze Jan Trdlička, 2012

PODPROGRAMY PROCEDURY A FUNKCE

Popis ovládání. Po přihlášení do aplikace se objeví navigátor. Navigátor je stromově seřazen a slouží pro přístup ke všem oknům celé aplikace.

Programovací jazyk Pascal

Úvod do informatiky. Miroslav Kolařík

Programovací í jazyk Haskell

Funkce, podmíněný příkaz if-else, příkaz cyklu for

Prolog PROgramming in LOGic část predikátové logiky prvního řádu rozvoj začíná po roce 1970 Robert Kowalski teoretické základy Alain Colmerauer, David

Připravil: David Procházka. Vertex Buffer Objects

1. Implementace funkce počet vrcholů. Předmět: Algoritmizace praktické aplikace (3ALGA)

Dynamicky vázané metody. Pozdní vazba, virtuální metody

Objektové programování

Vyučovací hodina. 1vyučovací hodina: 2vyučovací hodiny: Opakování z minulé hodiny. Procvičení nové látky

typová konverze typová inference

for (i = 0, j = 5; i < 10; i++) { // tělo cyklu }

Algoritmy a datové struktury

Algoritmizace prostorových úloh

Programujeme v softwaru Statistica

Transkript:

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