Memoisering, utsatt evaluering og strømmer

Like dokumenter
Memoisering, utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Mer om strømmer

INF2810: Funksjonell Programmering. Mer om strømmer

INF2810: Funksjonell Programmering. Strømmer og utsatt evaluering

INF2810: Funksjonell Programmering. Strømmer og utsatt evaluering

Gjennomgåelse av eksamensoppgaven i HUMIT2710 fra våren 2004

UNIVERSITETET I OSLO

(define (naer-nok-kuberot? y x) (< (abs (- (kube y) x)) 0.001)) (define (naermere-kuberot y x) (/ (+ (* y 2) (/ x (kvadrat y))) 3))

INF2810: Funksjonell Programmering. Strømmer

INF2810: Funksjonell Programmering. Strømmer

Side 1. Oppgave 1. Prosedyrer 1.1. Prosedyrene f og g skal begge returnere prosedyrer. a. Skriv f slik at ((f a) b) returnerer summen av a og b.

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering. Kommentarer til prøveeksamen

Memoisering. I de følgende memoiseringeksemplene brukes tabeller, og vi tar derfor først en repetisjon av dette.

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

Par og Lister (først et par sider fra forrige uke) Par er byggestener for lister og trær og sammensatte datatyper.

INF2810: Funksjonell Programmering

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering

Rekursjon og lister. Stephan Oepen & Erik Velldal. 1. februar, Universitetet i Oslo

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme, del 2

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Mer om verditilordning. Tabeller. Og strømmer.

INF2810: Funksjonell Programmering. En metasirkulær evaluator, del 2

INF2810: Funksjonell Programmering. En metasirkulær evaluator, del 2

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Mer om verditilordning og muterbare data.

INF2810: Funksjonell Programmering. Mer om verditilordning og muterbare data.

INF2810: Funksjonell Programmering. Dataabstraksjon og Trerekursjon

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme, del 2

Høyere-ordens prosedyrer

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Mer om verditilordning. Tabeller. Og strømmer.

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme, del 2

INF2810: Funksjonell Programmering. Lokale variabler. Og trær.

INF2810: Funksjonell Programmering. Lokale variabler. Og trær.

Eksamen i HUMIT 2710, Funksjonell programmering, våren Ingen hjelpemidler er tillatt. <resten av forsiden> Side 1 av 7

INF2810: Funksjonell programmering: Mer om Scheme. Rekursjon og iterasjon.

INF2810: Funksjonell Programmering. Eksamensforberedelser

INF2810: Funksjonell Programmering

Appendiks A Kontinuasjoner

INF2810: Funksjonell Programmering

Eksamen i SLI230, vår 2003.

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

INF2810: Funksjonell Programmering. En metasirkulær evaluator

INF2810: Funksjonell Programmering. En metasirkulær evaluator

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

Innlevering 2b i INF2810, vår 2017

Par og Lister (først et par sider fra forrige uke) Par er byggestener for lister og trær og sammensatte datatyper.

INF2810: Funksjonell Programmering. Tilstand og verditilordning

INF2810: Funksjonell Programmering. Køer, tabeller, og (litt om) parallelitet

INF2810: Funksjonell Programmering. Køer, tabeller, og (litt om) parallelitet

Memoisering. I de følgende memoiseringeksemplene brukes tabeller, og vi tar derfor først en repetisjon av dette.

INF2810: Funksjonell Programmering. Mer om Scheme. Rekursjon og iterasjon.

INF2810: Funksjonell Programmering. Muterbare data

INF2810: Funksjonell Programmering. Tilstand og verditilordning

INF2810: Funksjonell Programmering. Tilstand og verditilordning

Innlevering 2a i INF2810, vår 2017

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Mengder og lokal tilstand

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

Vi skal se på lambda-uttrykk. Følgende er definerte og vil bli brukt gjennom oppgaven

UNIVERSITETET I OSLO

LISP PVV-kurs 25. oktober 2012

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

Det er ikke tillatt med andre hjelpemidler enn de to sidene som er vedlagt oppgavesettet. Følgende funksjoner er definert og brukes i oppgaven:

INF2810: Funksjonell Programmering. Tilstand og verditilordning

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

Sideeekter og makroer i Lisp

IN1000 Obligatorisk innlevering 7

Python: Løkker. TDT4110 IT Grunnkurs Professor Guttorm Sindre

TDT Øvingsforelesning 1. Tuesday, August 28, 12

INF2810: Funksjonell Programmering. Mer om Scheme. Rekursjon og iterasjon.

Moderne Funksjonell Programmering i Lisp

INF1000 EKSTRATILBUD. Stoff fra uke 1-5 (6) 3. oktober 2012 Siri Moe Jensen

Lisp 2: Lister og funksjoner

NB! Sidene er mer eller mindre de samme som sidene i første forelesning

Øvingsforelesning 5 Python (TDT4110)

Løse reelle problemer

Oppsummering fra sist

Øvingsforelesning 5 Python (TDT4110)

INF2810: Funksjonell Programmering. Mer om Scheme. Rekursjon og iterasjon.

INF1010 notat: Binærsøking og quicksort

Oppgave 1 Minimum edit distance

Innhold uke 4. INF 1000 høsten 2011 Uke 4: 13. september. Deklarasjon av peker og opprettelse av arrayobjektet. Representasjon av array i Java

Obligatorisk oppgave 1 INF1020 h2005

Rekursjon som programmeringsteknikk

BOKMÅL Side 1 av 7. KONTINUASJONSEKSAMEN I FAG TDT4100 Objektorientert programmering / IT1104 Programmering, videregående kurs

Informasjon Eksamen i IN1000 og IN1001 høsten a) 1 poeng. 1b) 1 poeng. Tid. Oppgavene. Tillatte hjelpemidler. 30. november kl. 14.

Løse reelle problemer

Transkript:

Memoisering, utsatt evaluering og strømmer Først litt repetisjon: Utsatt evaluering Gitt (define (p x) (if test (x) something-else)) la E være et Scheme-uttrykk, og la L = (lambda () E). Da vil, ved kallet (p L), L bli evaluert uten forbehold, mens E bare blir evaluert hvis test #t. Spesialformene if, cond, and og or evalueres etter normal orden, som i praksis vil si at "argumentene" til disse formene gjøres til gjenstand for utsatt evaluering. 499

Memoisering: Tabellisering: Vi pakker inn en prosedyre p i en prosedyre q som har samme aritet som p og (ariteten til p er det antall argumenter p tar) en resultattabell i sin lokale omgivelse. la A være et sett med aktuelle argumenter til q. Ved første kall (q A) vil ikke tabellen ha noe entry med A som nøkkel, og dermed utføres kallet(p A), og resultatet lagres i tabellen med A som oppslagsnøkkel, før det returneres. Ved alle etterfølgende kall (q A) finner vi det tidligere beregnede resultatet i tabellen ved oppslag på A, og vi returnerer dette. 500

Hvis ariteten til p = 0 bruker vi en enkelt variabel r for resultatet, (vi bruker ikke en 0-dimensjonal tabell) men siden r alltid vil være der, og verdien #f kunne være et mulig resultat, trenger vi i tillegg et flagg som heises når resultatet beregnes. Utsatt evaluering vha. lambda Her består argumentet til q, her kalt memo-proc, i et argumentløst lambda-uttrykk med det aktuelle uttrykket, f.eks. kallet på p, som kropp. (define (memo-proc lambda-wrapped-expression) (let ((already-run? #f) ; begge disse verdiene gjelder (result #f)) ; bare frem til første kall (lambda () (if already-run? result (begin (set! already-run? #t) (set! result (lambda-wrapped-expression))); utfør det innpakede uttrykket result))) 501

Utsatt evaluering vha. spesialformen delay. Her skjer noe tilsvarende som beskrevet over, bortsett fra at spesialformen delay tar imot selve uttrykket, f.eks. et kall på p, uten lambda-innpakning. Vi kaller returverdien fra delay et løfte, og det som loves er en evaluering av uttrykket, om det skulle kreves. Vi kunne ha definert vår egen delay, slik: (define-syntax delay (syntax-rules () ((delay expression) (memo-proc ; se under (lambda () expression))))) Men merk at løfte er noe annet enn en prosedyre, hvilket vil si at returverdiene fra den forhåndsdefinerte delay og vår egen versjon av delay er av ulike typer. For å få innfridd et løfte, må vi bruke prosedyren force. Vi kan ikke ganske enkelt kalle det. 502

Strømmer cons-stream er en spesialform definert vha. delay. (define-syntax cons-stream (syntax-rules () ((cons-stream obj stream) (cons obj (delay stream))))) cons-stream tar to argumenter x og y og returnerer paret med ferdig evaluert x i car-delen og et løfte om evalueringen av y i cdr-delen. Parets car-del aksesseres ved selektoren car, men mht. strømabstraksjonen, gir vi selektoren et eget navn: stream-car. Parets cdr-del aksesseres ved løfteavkrevingsprosedyren force, men mht. strømabstraksjonen, gir vi selektoren et eget navn: stream-cdr. 503

Strømmen av heltall (define (int-stream n) (cons-stream n (int-stream (+ n 1)))) (define integers (int-stream 1)) <- n promises of n + 1 1 <- 1 2 3 4... 1 2 <- 2 3 4 5... 1 2 3 <- 3 4 5 6... 1 2 3 4 <- 4 5 6 7... 1 2 3 4 5 <- 5 6 7 8... 1 2 3 4 5 6 <- 6 7 8 9... 1 2 3 4 5 6 7 <- 7 8 9 10... Strømmen av primtall (define (prime-stream S) (if (prime? (stream-car S) (cons-stream (stream-car S)) (prime-stream (stream-cdr S)) (prime-stream (stream-cdr S)))) (define primes (prime-stream integers) <- prime? promises of integers 1 2 3 4... 2 <- 2 3 4 5... 2 3 <- 3 4 5 6... 4 5 6 7... 2 3 5 <- 5 6 7 8... 6 7 8 9... 2 3 5 7 7 8 9 10... 504

Strømmen av kvadrerte primtall (define (square-stream S) (cons-stream (square (stream-car S)) (square-stream (stream-cdr S)) (square-stream primes) <- square promises of primes 4 <- 2 3 5 7... 4 9 <- 3 5 7 11... 4 9 25 <- 5 7 11 13... 4 9 25 49 <- 7 11 13 17... 4 9 25 49 121 <- 11 13 17 19... 4 9 25 49 121 169 <- 13 17 19 23... 4 9 25 49 121 169 289 <- 17 19 23 29... 505

(square-stream (prime-stream (int-stream 1))) <- square <- prime? <- int promises of n + 1 1 <- 1 2 3 4... 4 <- 2 <- 2 <- 2 3 4 5... 4 9 <- 3 <- 3 <- 3 4 5 6... 4 <- 4 5 6 7... 4 9 25 <- 5 <- 5 <- 5 6 7 8... 6 <- 6 7 8 9... 4 9 25 49 <- 7 <- 7 <- 7 8 9 10... 8 <- 8 9 10 11... 9 <- 9 10 11 12... 10 <- 10 11 12 13... 4 9 25 49 121 <- 11 <- 11 <- 11 12 13 14... 12 <- 12 13 14 15... 4 9 25 49 121 169 <- 13 <- 13 <- 13 14 15 16... 14 <- 14 15 16 17... 15 <- 15 16 17 18... 16 <- 16 17 18 19... 4 9 25 49 121 169 289 <- 17 <- 17 <- 17 18 19 20... 506

Typiske listeoperasjoner overført på strømmer Til dataabstraksjonen for strømmer hører: (define stream-nil '()) (stream-null? stream) ; null?. Returnerer true hvis strømmen er tom. (cons-stream første-element resten) (stream-car strøm) ; returnerer første element umiddelbart (stream-cdr strøm) ; fremtvinger produksjonen av neste element fra strømmen Ved hjelp av disse kan vi definer strøm-utgaver av de fleste standardoperasjoner for lister. (define (stream-ref S n) (if (= n 0) (stream-car S) (stream-ref (stream-cdr S) ( - n 1)))) (define (stream-map proc S) (if (stream-null? S) stream-nil (cons-stream (proc (stream-car S)) (stream-map proc (stream-cdr S))))) 507

(define (stream-filter pred S) (cond ((stream-null? S) stream-nil) ((pred (stream-car S)) (cons-stream (stream-car S) (stream-filter pred (stream-cdr S)))) (else (stream-filter pred (stream-cdr S))))) stream-map og stream-filter skal selv produserer strømmer, enten den tomme strømmen eller et strøm-par (med direkte tilgjengelig car-del og et løfte i cdr-delen) enten ved bruk av cons-stream, eller ved kall på en strømgenererende prosedyre. Dette gjelder også lignende prosedyrer som vi definerer selv. Her skal alle konsekventer og alternativer i en prosedyre med flere mulige utfall, angitt ved if-, cond-, and- og or-uttrykk, returnere strømmer (nokså selvfølgelig, men allikevel mulig å overse). 508

Følgende prosedyre produserer ingen strøm, men en serie av hendelser: (define (stream-for-each proc S n) (if (or (stream-null? S) (zero? n)) 'done (begin (proc (stream-car S)) (stream-for-each proc (stream-cdr S) (- n 1))))) For å kontrollere at vi har satt opp den ene eller den andre strømmen riktig, kan det være greit å ha en rutine som denne. (define (stream->list S n) (if (or (stream-null? S) (zero? n)) '() (cons (stream-car S) (stream->list (stream-cdr S) (- n 1))))) NB! Langt de fleste av de strømmene vi skal se på, er uendelige, noe som vil gitt en evig løkke dersom stream->for-each og stream->list ikke hadde hatt en teller (her n). 509

Her er enda et par nyttige rutiner: (ikke i læreboka) list->stream gjør, som navnet tilsier, det motsatte av stream->list. (define (list->stream L) (if (null? L) stream-nil (cons-stream (car L) (list->stream (cdr L))))) stream-tail gjør nesten det samme som stream-ref, bortsette fra at den returnerer første par, snarere enn første verdi, i en gitt avstand fra begynnelsen av strømmen, (define (stream->tail S n) (if (= n 0) S (stream-tail (stream-cdr S) (- n 1)))) og i og med at returnerer et par, returnerer den også resten av lista halen fra og med n'te par. 510

Uendelige strømmer (egentlige er dette det generelle, mens endelige strømmer er, som sådanne, spesielle) Enumererte tall (define (integers-from n) (cons-stream n (integers-from (+ n 1)))) (define integers (integers-from 1)) Fibonacci-tall Fibonaccifunksjonen (define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) Fibonaccistrømmen (define (fibgen this next) (cons-stream this (fibgen next (+ this next)))) (define fibs (fibgen 0 1)) (stream->list fibs 10) (0 1 1 2 3 5 8 13 21 34) 511

Når fibonaccifunksjonen implementeres i en prosedyre, må vi sjekke for basistilfellet, og i den iterative varianten må vi, for å få med basistilfellet, hele tiden beregne leddet etter det vi skal returnere. (define (fib-iter this prev i) (if (= i 0) prev (fib-iter (+ this prev) this (- i 1)))) (map (lambda (n) (fib-iter 1 0 n)) (0 1 2 3 4 5)) 0 1 1 2 3 5 utregnet 3 + 5 prev this I strøm-varianten trenger vi ikke å sjekke forbasistilfellet, eller mer presist: vi starter med basistilfellet, i første kall på strømgeneratoren, og deretter utvikles strømmen idet vi aksesserer dens enkelte ledd, uten sjekk for noe termineringskriterium. (define (fibgen this next) (cons-stream this (fibgen next (+ this next)))) (stream->list (fibgen 0 1) 5) 0 1 1 2 3 5 utsatt 3 + 5 this next 512

Tallrekker med ut-filterte faktorer La oss se på en strøm som filtrerer ut fra heltallsstrømmen alle tall som inneholder en gitt faktor. Vi trenger da et delelighetspredikat. (define (divisible? x y) (= (remainder x y) 0)) For å se hvordan det tar seg ut, lager vi først en spesifikk strøm med alle tall som ikke har 7 som faktor. (define no-sevens (stream-filter (lambda (x) (not (divisible? x 7))) integers)) (inf-stream->list no-sevens 19) (1 2 3 4 5 6 8 9 10 11 12 13 15 16 17 18 19 20 22) Her er 7, 14 og 21 ikke med. 513

Så generaliserer vi dette til en heltallsstrøm, der vi lar faktoren være en variabel. (define (no-factor-f f) (stream-filter (lambda (x) (not (divisible? x f))) integers)) (define no-sevens (no-factor-f 7)) Vi kan generalisere ytterligere slik at generatoren, i stedet for den spesifikke strømmen integers, også tar matestrømmen som argument. (define (no-factor-f f int-stream) (stream-filter (lambda (x) (not (divisible? x f))) int-stream)) (define no-sevens (no-factor-f 7 integers)) (define no-threes-or-fives (no-factor-f 5 (no-factor-f 3 integers))) (stream->list no-threes-or-fives 10) (1 2 4 7 8 11 13 14 16 17) 514

Om vi nå sier at ingen tall i strømmen skal ha som faktor noe tidligere tall fra strømmen får vi følgende prosedyre (define (unique-factors S) (cons-stream (stream-car S) ; Vi har nå brukt faktoren (stream-car S) og ønsker ikke å se mer til den, (unique-factors (no-factor-f (stream-car S) ; så vi fjerner den fra (stream-cdr S)))))) ; den etterfølgende strømmen Det prinsippet vi her har implementert, er kjent som Eratosthenes' sil. - Om vi, gitt heltallsstrømmmen fra og med 2, - først filtrerer bort alle tall etter 2 som er delelig med 2 og - deretter filtrerer bort alle tall etter det første gjenværende som er delelig med dette, osv, så står vi igjen med strømmen av de tall som ikke er delelig med noen av sine forgjengere altså primtallene. 515

Lærebokas versjon ser slik ut: (define (sieve S) (cons-stream (stream-car S) ; Første gjenværende, her kalt p (sieve (stream-filter ; Filtrer ut (lambda (x) ; de tall som har (not (divisible? x (stream-car S)))) ; p som faktor (stream-cdr S))))) ; fra de gjenværende tallene etter p. (define primes (sieve (stream-cdr integers))) 516

Implisitte strømmer Forskjellen mellom implisitte og eksplisitte strømmer kan illustreres ved følgende: (define (gjenta) (cons-stream 'jada (gjenta))) ; eksplisitt (define repetér (cons-stream 'jada repetér)) ; implisitt Disse oppfører seg helt likt, bortsett fra at den første kalles, mens den andre bare nevnes. (car (gjenta)) jada (car (stream-cdr (gjenta))) jada (car (stream-cdr (stream-cdr (gjenta)))) jada (stream->list (gjenta) 3) (jada jada jada) (car repetér) jada (car (stream-cdr repetér)) jada (car (stream-cdr (stream-cdr repetér))) jada (stream->list repetér 3) (jada jada jada) 517

Parvis summering av to strømmer Vi bruker den eksplisitte formen når vi ønsker noe mer enn rene gjentagelser, men det betyr ikke at implisitte strømmer er trivielle. Her er en tilsynelatende triviell implisitt strøm: (define enere (cons-stream 1 enere)) ; produserer 1 1 1 1... Denne kan vi kombinere med følgende eksplisitte strøm til å gi oss enumereringen av heltallene: (define heltall (cons-stream 1 (add-streams enere heltall)) når add-streams er definert slik: Eks: (define (add-streams s1 s2) (stream-map + s1 s2)) (add-streams enere enere) ; produserer 2 2 2 2... 518

Strømmen heltall er definert over som - tallet 1 fulgt av - de parvise summene av 1 og neste heltall. (define heltall (cons-stream 1 (add-streams enere heltall)) For å skjønne hva som skjer, ser vi på realiseringen av strømmen og dens addender: enere 1 1 1 1 1 1 1 1 + + + + + + + + heltall 1 2 3 4 5 6 7 8 heltall 1 2 3 4 5 6 7 8 9 Når add-streams kalles første gang med heltall som argument, er første ledd i heltall allerede produsert. Ved andre gangs kall på add-streams, er andre ledd i heltall produsert, osv. 519

Dette inspirere til en ny måte å definere fibonacci-tallene på (define fibs (cons-stream 0 (cons-stream 1 (add-streams fibs (stream-cdr fibs))))) Her er mønstret litt mer komplisert enn for heltallstrømmen. - Vi starter vi med to kjente tall i stedet for ett, og - i stedet for å legge sammen parvis enere og suksessive heltall, så legger vi sammen parvis løpende og neste fibonaccitall. fibs 0 0 1 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 + + + + + + + + + + (stream-cdr fibs) 1 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 55 A fibs 0 0 1 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 55 A 89 C Når add-streams kalles første gang med fibs som første og (stream-cdr fibs) som andre argument, er både første og andre ledd i fibs allerede produsert. Ved andre gangs kall på add-streams, er tredje ledd i fibs produsert, osv. 520

Skalering av strømmer Følgende strøm tar en tallstrøm som input og skalerer denne ved å multiplisere hvert element i med en gitt faktor. (define (scale stream stream factor) (stream map (lambda (x) (* factor x)) stream)) (scale stream heltall 2) 2 4 6 8... (define double (cons stream 1 (scale stream double 2))) double 1 2 4 8 16 32 64... Vi kan visualisere hva som foregår i strømmen double slik: 2 2 2 2 2 2 2...... 1 2 4 8 16 32 64... 1 2 4 8 16 32 64 128... 521

522

Primtallene som en implisitt strøm et alternativ til Eratosthenes sil Vi bruker heltallsstrømmen som basis hele veien og filtrere vekk de tallene som ikke er primtall fra denne ved hjelp av den primtallsstrømmen vi genererer. Vi lar da det første primtallet 2 være car element i utgangspunktet og bruker heltallene fra 3 som inputstrøm for genereringen av resten. (define primes (cons stream 2 (stream filter prime? (heltall fra 3)))) Poenget her er at testen i prime? utføres i forhold til den selvsamme strømmen vi genererer, og ikke er basert på en ekstern metode à la Miller Rabin testen. (define (prime? n) (define (iter ps) (cond ((> (square (car ps)) n)) ; primtall ((= (remainder n (car ps)) 0) #f) ; ikke primtall (else (iter (stream-cdr ps))))) ; kanskje primtall (iter primes)) 523

Det tallet som filtreres bort i iterasjonen i prime?, hentes selv fra primtallsstrømmen, men hvordan kan en strøm som bare produserer primtall, gi fra seg et ikke-primtall for filtrering? Poenget er at primes er basert på heltallsstrømmen, slik at det tallet som iter henter fra primes er neste heltall, før det er filtrert ut, men dette kommer ikke videre til den som aksesserer primes, med mindre det slipper gjennom testen i primes?. 524

For å forstå poenget med første ledd i cond-setningen i iter, kan vi se på en alternativ utforming (define (prime? n) (define (iter ps) (cond ((> (car ps) (/ n (car ps)))) ; primtall ((= (remainder n (car ps)) 0) #f) ; ikke primtall (else (iter (stream-cdr ps))))) ; kanskje primtall (iter primes)) Testen (> (car ps) (/ n (car ps))) er i realiteten den samme som (> (square (car ps)) n)) Gitt to heltall a og b, hvis a 2 > b så er a > b/a, og omvendt. Betydningen av testen er da som følger: Hvis a er et primtall og a < b/a, så finnes det kanskje et primtall c > a, slik at c b/c, men hvis a < b/a, så kan det ikke finnes noen slik c. 525

157, 163 og 173 er alle primtall, men hva med tallene i mellom (vi sjekker bare oddetallene)? 3 159, 7 161, 3 165, (x y betyr "x deler y" "y er delelig med x") ingen av 2, 3, 5, 7, 11 deler 167, og 167 / 13 < 13, og dermed er det ingen tall større enn 13 som deler 167, ergo er 167 et primtall. Deretter ser vi at 13 169 og 3 171, ergo er 167 det eneste primtallet mellom 163 og 173. 526

Utnyttelser av strøm paradigmet Iterasjon i imperative og funksjonelle språk C, C++, Java,... Scheme int sigma(int n) (define (sigma n) { int S = 0; (define (loop i S) for (int i = 1; i <= n; i++) (if (<= i n) S += i; (loop (+ i 1) (+ S i)) return S; S)) } (loop 1 0)) int fib(int n) (define (fib n) { int a = 1, b = 0; (define (loop i a b) for (int i = 1; i <= n; i++) if (<= i n) { int f = a + b; (loop (+ i 1) b = a; (+ a b) a = f; a) } b)) return b; (loop 1 1 0)) } 527

Med strømmer kan vi gjøre det slik (define sigmas (cons-stream 0 (add-streams integers sigmas))) (define fibs (cons-stream 0 (cons-stream 1 (add-streams (stream-cdr fibs) fibs)))) Vi tar med add-streams for å vise at det ikke ligger noen tellere bak kulissene (define (add-streams S1 S2) (cons-stream (+ (stream-car S1) (stream-car S2)) (add-streams (stream-cdr S1) (stream-cdr S2)))) 528

Strømmen skiller seg vesentlig fra de andre implementasjonene ved at den, ikke har noe basistilfelle. Det er alltid ett ledd til. Dette kommer tydelig frem i kvadratrotstilnærmingen i SICP 1.1.7 (noe komprimert her). (define (square-root x) (define (loop y) (if (>= (abs (- (* y y) x)) 0.001) ; Er vi ennå ikke nær nok? (loop (/ (+ (/ x y) y) 2)) ; så looper vi videre. y)) ; Vi er nær nok og returnerer siste gjetning. (loop 1.0)) Strømversjonen har ingen test for om vi har kommet nær nok. (define (sqrt-stream x) (define guess-stream (cons-stream 1.0 (stream-map (lambda (y) (/ (+ (/ x y) y) 2)) guess-stream))) guess-stream) 529

Det finnes tilstandsbaserte sekvenser der enkel iterasjon, uansett paradigme (imperativt eller funksjonelt), gir en lite hensiktsmessig løsning, fordi vi ønsker både å ha lokal kontroll over de relevante variablene og å holde tilstandsinformasjonen skjult, samtidig som vi vil at output fra sekvensen skal være globalt tilgjengelig. Men fremfor alt ønsker vi å unngå at tilstandsvariabler sendes frem og tilbake mellom oppdateringsprosedyrer og brukerprosedyrer. Stjerneeksemplet er en randomgenerator, der løpende random-nummer er en funksjon av foregående. 530

Random-sekvenser er strømmer En random-generator er en sekvens x 0, x 1, x 2, med en tilhørende oppdateringsfunksjon f, slik at x n = f(x n 1 ). I et strengt funksjonelt program må vi sende f og x n 1 rundt omkring, fra den ene prosedyren til den andre. Dette er lite heldig, så vi bryter vi med det funksjonelle paradigme, og lager et prosedyreobjekt med x som intern tilstandsvariabel. En randomsekvens har imidlertid et vesentlig trekk felles med en strøm, idet den ikke har noe basistilfelle. Har vi først har startet en randomgenerator, er det alltid et tall til å hente. 531

I en random-strøm kjenner hvert ledd sin forgjenger, på samme måte som en rendomgenerator til enhver tid kjenner sitt sist genererte tall. La rand-init være startverdien i en randomsekvens, og la prosedyren rand-update som produsere de etterfølgende tallene i sekvensen. Vi kan da definere en randomgenerator som et prosedyreobjekt med lokal tilstand slik (define rand (let ((x rand-init)) (lambda () (set! x (rand-update x)) x))) og vi kan definere den tilsvarende randomstrømmen slik (define rand-stream (cons-stream rand-init (stream-map rand-update rand-stream))) 532

Randomgeneratoren og randomstrømmen virker på samme måte i den forstand at rand har forrige genererte random-tall x i sin omgivelse, og rand-stream får tak i forrige genererte random-tall ved å aksessere seg selv. R ==> 2 17 87 9 25 36 21 79 map rand-update R ==> 17 87 9 25 36 21 79 2 17 87 9 25 36 21 79... ri (ru 2) (ru 17) (ru 87) (ru 9) (ru 25) (ru 36) (ru 21)... Men rand-stream er ikke mer tilstandsbasert enn en hvilken som helst annen strøm som utvikles ved rekursiv gjenbruk av seg selv. Eks: (define enere (cons-stream 1 enere)) (define heltall (cons-stream 1 (add-streams enere heltall))) enere 1 1 1 1 1 1 1 1 + + + + + + + + heltall 1 2 3 4 5 6 7 8 heltall 1 2 3 4 5 6 7 8 9 Som alle andre funksjoner gir rand-update alltid samme resultat med samme argument. 533

534

En strøm av monte-carlo-verdier (se SICP 3.1.2 og forelesning 8) Vi kan implementere monte-carlo-simulering som en strøm som tar en strøm av eksperimenter (tester) som argument. (define (monte-carlo test-stream passed failed) (define (next passed failed) (cons-stream (/ passed (+ passed failed)) (monte-carlo (stream-cdr test-stream) passed failed))) (if (stream-car test-stream) (next (+ passed 1) failed) (next passed (+ failed 1)))) Den argumentløse testprosedyren experiment, som brukes i prosedyreimplementasjonen, og som ved gjentatte kall gir en boolesk sekvens, er i strømimplementasjonen erstattet med strømmen test-stream, som gir samme sekvens, hvis det initielle randomtallet er det samme. 535

Til forskjell fra monte-carlo-implementasjonene i kapittel 3.2, gir hvert ledd i strømmen et simuleringsresultat, dvs. gitt en monte-carlo-strøm, har vi for hvert ledd k resultatet av k eksperimenter, mens vi i kapittel 3.2 starter med et gitt antall eksperimenter og ikke har noe simuleringsresultat før alle eksperimentene er utført. Med samme initielle randomtall vil par nummer 1000 i randomstrømmen ha samme verdi, som det randomgeneratoren returnerer fra kall nummer 1000. Men, siden antall forsøk ikke er kjent for strømmen på forhånd, må strømmen selv telle opp alle forsøk noe den gjør ved å telle både vellykkede og mislykkede forsøk. 536

Cesaro-testen går som kjent ut på at sannsynligheten for at to vilkårlige tall a og b ikke har noen felles primtallsfaktorer = 6/ 2. For å produsere eksperimentstrømmen til monte-carlo-simuleringen definere vi en strøm som henter to og to elementer fra en annen gitt strøm og anvender en gitt binær funksjon på disse: (define (map-successive-pairs f S) (cons-stream (f (stream-car S) (stream-car (stream-cdr S))) ; dette og neste (map-successive-pairs f (stream-cdr (stream-cdr S))))) ; to videre Kaller vi denne med cesaro-testen og randomstrømmen som argumenter, får vi i retur den eksperimentstrømmen vi trenger for at monte-carlo skal gi oss en tilnærming til. 537

(define cesaro-stream (map-successive-pairs (lambda (r1 r2) (= (gcd r1 r2) 1)) rand-stream)) Eksempel: pairs (7 8 3 6 18 5 7 1 25 15 10 23 11 3 12 3 44 26 17 20 18 21 6 27... ) (map-successive-pairs (lambda (x y) (> (gcd x y) 1) pairs) ===> test-stream (#t #f #t #t #f #t #t #f #f #t #f #t... ) n-passed 1 1 2 3 3 4 5 5 5 6 6 7... ) n-failed 0 1 1 1 2 2 2 3 4 4 5 5... ) n-tests 1 2 3 4 5 6 7 8 9 10 11 12... ) n-passed 1 0.5 0.66. 0.75 0.6 0.66 0.7 0.625 0.55.. 0.6 0.54.. 0.58..... ) n-tests Det vi helst skal frem til er 6/ 2 0.6079 hvilket tar langt mer enn 12 forsøk. 538