Informasjonsteori og entropi



Like dokumenter
Informasjonsteori og entropi

INF2810: Funksjonell Programmering. Huffman-koding

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Huffman-koding

INF2810: Funksjonell Programmering. Huffman-koding

INF2810: Funksjonell Programmering. Mengder og lokal tilstand

INF2810: Funksjonell Programmering. Huffmankoding

INF2810: Funksjonell Programmering. Huffman-koding

INF2810: Funksjonell Programmering. Dataabstraksjon og Trerekursjon

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

INF2810: Funksjonell Programmering. Trær og mengder

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

INF2810: Funksjonell Programmering. Trær og mengder

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. Kommentarer til prøveeksamen

INF2810: Funksjonell Programmering. Huffman-koding

INF2810: Funksjonell Programmering. Tilstand og verditilordning

INF2810: Funksjonell Programmering

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

INF2810: Funksjonell Programmering. Tilstand og verditilordning

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Tilstand og verditilordning

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

IN2040: Funksjonell programmering. Trær, mengder og huffmankoding

INF2810: Funksjonell Programmering. Huffman-koding

INF2810: Funksjonell Programmering. Tilstand og verditilordning

INF2810: Funksjonell Programmering. Muterbare data

Innlevering 2a i INF2810, vår 2017

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

INF2810: Funksjonell Programmering

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

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

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

INF2810: Funksjonell Programmering

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

Definisjon av binært søketre

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

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

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

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

Innlevering 2b i INF2810, vår 2017

INF2810: Funksjonell Programmering. Strømmer og utsatt evaluering

INF2810: Funksjonell Programmering. En metasirkulær evaluator

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

Definisjon: Et sortert tre

Binære søketrær. Et notat for INF1010 Stein Michael Storleer 16. mai 2013

Oppgave 1 Minimum edit distance

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

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

Oppsummering. MAT1030 Diskret matematikk. Oppsummering. Oppsummering. Eksempel

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering. En metasirkulær evaluator

Høyere-ordens prosedyrer

MAT1030 Diskret Matematikk

(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. Eksamensforberedelser

UNIVERSITETET I OSLO

E K S A M E N. Algoritmiske metoder I. EKSAMENSDATO: 11. desember HINDA / 00HINDB / 00HINEA ( 2DA / 2DB / 2EA ) TID:

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

INF Algoritmer og datastrukturer

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

Sist forelesning snakket vi i hovedsak om trær med rot, og om praktisk bruk av slike. rot. barn

INF2810: Funksjonell Programmering. Strømmer og utsatt evaluering

MAT1030 Diskret matematikk

... Når internminnet blir for lite. Dagens plan: Løsning: Utvidbar hashing. hash(x) katalog. O modellen er ikke lenger gyldig ved

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

UNIVERSITETET I OSLO

Memoisering, utsatt evaluering og strømmer

Memoisering, utsatt evaluering og strømmer

Repetisjon: Binære. Dagens plan: Rød-svarte trær. Oppgave (N + 1)!

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

Binære trær: Noen algoritmer og anvendelser

INF2810: Funksjonell programmering: Introduksjon

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

Et eksempel: Åtterspillet

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2220: Forelesning 1. Praktisk informasjon Analyse av algoritmer (kapittel 2) (Binær)trær (kapittel )

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Mer om strømmer

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Mer om strømmer

Eksamen iin115, 14. mai 1998 Side 2 Oppgave 1 15 % Du skal skrive en prosedyre lagalle som i en global character array S(1:n) genererer alle sekvenser

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

Hva er en algoritme? INF HØSTEN 2006 INF1020. Kursansvarlige Ragnar Normann E-post: Dagens tema

Dagens plan. INF Algoritmer og datastrukturer. Koding av tegn. Huffman-koding

INF1020 Algoritmer og datastrukturer GRAFER

Eksamen i SLI230, vår 2003.

UNIVERSITETET I OSLO

KONTINUASJONSEKSAMEN

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. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Muterbare data

Appendiks A Kontinuasjoner

UNIVERSITETET I OSLO

Transkript:

Informasjonsteori og entropi Termen entropi tilhører i utgangspunktet termodynamikken, der den, svært forenklet, betegner reduksjon av energipotensialet innenfor et system der det foregår enerigutveksling mellom delene. F.eks. to innsjøer A og B, med A høyere enn B, forbundet med et rør fra bunnen av A til B danner et system der energipotensialet er vannmengden i A. Begrenser vi oss til å betrakte systemets varme, finner vi at etter hvert som temperaturforskjellene synker, stiger entropien inntil den når et maksimum der det ikke lenger finnes noen temperaturforskjeller innenfor systemet. Fysikeren Rudolph Clausius (1822-88) laget ordet entropi etter mønster av ordet energi fra gresk, energos, "i arbeid" (dvs. "arbeidende"), satt sammen av en, "i", "ved" + ergon, "arbeid". Delen tropi er da ment å være fra gresk tropos, "vending". Det greske ordet entropi betyr " vending mot noe". Det Classius tenkte på var kanskje noe sånt som "det som er inneholdt i en endring", eller mer spesifikt "det som er inneholdt i et forfall". 293

Generelt kan vi si at et system med maksimum entropi er et fullstendig amorft system der det ikke finnes noen some helst signifikante forskjeller i noe som helst henseende, eller om vi vil: et katotisk system et system uten holdepunkter. I forhold til informasjonsteori kan vi se på entropi, ikke som tap av intern energi, men som behov for ekstern energi. Jo mer kaotisk et system er, desto mer energi eller altså informasjonsbiter kreves det for å få noe meningsfullt ut av det. 294

En verden der det alltid er vinter krever ikke et eneste bit. For a skille mellom vinter- og sommer, trenger vi ett bit som gir oss verdiene 0 og 1. For å skille mellom de tre skandinaviske land, klarer vi oss med to bits som i alle fall gir oss verdiene 01, 10, og 11. For å skille mellom de fire årstider, må vi ha to bits som gir oss verdiene 00, 01, 10, og 11. For å skille mellom årets maksimalt 53 uker, klarer vi oss med seks bits, som gir verdiene 000000... 110100 plus noen til (0... 52 desimalt). 295 Her er det en tilsynelatende motsetning mellom termodynamikk og informasjonsteori. Universets varmedød, entropiens maksimum, inntrer når alt har fått samme temperatur, når ingenting lenger skjer. Motsetningen forsvinner når vi innser at ingenting er det samme som alt på en gang, som er det stikk motsatte av nøyaktig én ting (og heri ligger visdommen i uttrykket "dette sier jo alt og ingenting"). En annen pussighet: Han som lanserte begrepet universets varmedød, Lord Kelvin (kjenning av Clausius), var også opphavsmannen til den temperaturskala som starter ved det absolutte nullpunkt og dette er, på en abstrakt måte, like langt fra varmedøden som temperaturen ved universets begynnelse. (Nå var jo universet i utgangspunktet nærmest uendelig lite, og varmen dermed tilnærmet uniformt distribuert, men universet var også nærmest uendelig ustabilt, og dermed fikk vi The Big Bang. Hvor stabilt universet ville være med en uniform temperatur på 0 o K, kan man jo bare spekulere på.)

Mer presist betegner termen entropi i informasjonsteorien den mengde informasjon en variabel størrelse kan sies å ha, eller med andre ord, det antall bits som må til for å kode variabelen entydig. Kodingen 01 for Danmark, 10 for Norge, 11for Sverige er (trivielt) entydig fordi 01 {10, 11}. 10 {01, 11}. 11 {01, 10}. Med 0 for Danmark hadde vi fremdeles hatt entydighet, fordi hverken 10 eller 11 begynner med 0, og dermed kunne vi ha nøyd oss med gjennomsnittlig 5/3 bits. På den andre siden: med to bits har systemet rom for et fjerde skandinavisk land, f.eks. Island, som kan kodes med 00. 296

Vi nedtegner resulatetene av en serie kast med en åttekantet "terning" (en oktaeder) 1. Vi lar X være det variable utkommet av de enkelte terningkast. Siden X kan ha én av 8 mulige verdier, og ett bit har to mulige verdier, og 8 = 2 3, trenger vi 3 bits for å beskrive verdiene til X entydig, eller m.a.o. X har en entropi = 3. 000 001 010 011 100 101 110 111 1 En bearbeiding av et eksempel i Manning & Schütze, 2002, Foundations of Statistical Natural Language Processing, MIT Press 297

Så hva med den mer typiske sekskantede terningen, når vi nummererer sidene fra 0 til 5? Nå trenger vi tre bits for hver av 4 og 5, hhv. 100 og 101, så også i dette tilfellet synes X å måtte ha en entropi = 3. Men tillater vi at ulike verdier av X uttrykkes med ulike antall bits, kan vi klare oss med gjennomsnittlig færre antall bits, så lengde kodingen er entydig. F.eks. er følgende rekke av verdier innbyrdes entydig fordi ingen av de tresifrede verdiene inneholder noen av de tosifrede som første del. 00 01 100 101 110 111 Ingen andre koder enn 00 starter med 00, og ingen andre koder enn 01 starter med 01. På tilsvarende måte kunne vi ha kodet systemet (Danmark, Norge, Sverige) slik 0 for Danmark, 10 for Norge, 11for Sverige. Merk at både 2 og 3 i og for seg kun trenger to bits hver, 10 og 11, men dette ville ha gitt flertydighet. 298

Bit-strengen 00101100010110100110111100 kan bare leses på én måte I dette tilfellet er entropien til X = (2 2 + 3 4)/6 = 8/3 2,66667 bits. 299

Ovenstående angivelse av en variabels entropi er presis, bare om vi går ut fra at alle variabelens mulige verdier er like sannsynlige. Går vi ut fra at terningen er skjev eller har ujevnt fordelt masse, må entropien uttrykkes som en funksjon av mengden av de ulike kastenes sannsynligheter. F.eks. dersom sidene med 2 og 4 øyne kommer opp dobbelt så ofte som de øvrige, får vi følgende fordeling for terningens ulike sider i 8 kast: Lar vi de to hyppigst forekommende verdiene kodes med 2 bits og de øvrige med 3, vil vi i det lange løp bruke i snitt (2 sider 2 bits 2 ganger + 4 sider 3 bits 1 gang)/8 ganger = 20/8 = 2.5 side-bits per gang, hvilket vil si 2.5 bits per kode hvilket vil si at entropien(x) = 2.5. 300

Generelt skal vi ha summen av produktene av bitantall og vekter delt på summen av vektene altså, når b i og v i er hhv. antall bits for, og vekten (hyppigheten) til, terning i: Tilsvarende er regnestykket over n n E(X) = ( b i v i ) / v i i=1 i=1 ((3 1) + (2 2) + (3 1) + (2 2) + (3 1) + (3 1)) / (1 + 2 + 1 + 2 + 1 + 1). Et tegnsystem som fremviser tilsvarende regelmessighet er morsekoden, der de mest brukte tegnene er kodet med færrest prikker og/eller streker. 301

Frekvenssortering Ordning av mengder etter elementenes verdier kan gi gevinst både ved ulike mengdesoperasjoner og ved søking, men i noen tilfeller kan det være vel så lurt å ordne mengden etter elementenes etterspørselsfrekvens, slik at de elementene som etterspørres mest, legges først. I informasjonsteorien bruker vi frekvensene til tegnene i et system til å beregne den optimale kodingen av systemet, idet vi bruker færrest bits på de mest frekvente og flest bits på de minst frekvente tegnene. Dette gir en komprimeringsgevinst, men også en tidsgevinst, dvs. den optimale kodingen gir en ordning i den forstand at bitlengden til en kode bestemmer hvor lang tid det tar å lese den. Huffmann-koding gir både komprimering og tidsbesparelse. 302

Huffmann-koding SICP 2.3.4 David Huffmann utviklet en algoritme for en optimal fordeling avantall bits innenfor et alfabet i henhold til tegnenes bruksfrekvens. 2 En Huffman-kode kan representeres ved et binært tre. - Hvert subtre inneholder - en liste over alle tegnene i subtreet. - disse tegnenes samlede vekt og - et venstre og et høyre subtre, hvorav ett eller begge kan være blader. - Hvert blad inneholder - et kodet tegn og - dets vekt. Vektene brukes til å organisere treet optimalt med hensyn til koding og avkoding av tegn, men vektene brukes ikke under selve kodingen og avkodingen. 2 Huffmanns leverte dette som en semetseroppgave i et kurs i informasjonsteori. 303

Gitt et 8-tegns alfabet med følgend relative bruksfrekvenser: A:8 B:3 C:1 D:1 E:1 F:1 G:1 H:1 Med følgende optimale koding A:0 B:100 C:1010 D:1011 E:1100 F:1101 G:1110 H:1111 får vi en tegnvis entropi på (1 8 1 + 1 3 3 + 6 1 4)/28 = 41/17 = 2 Det tilsvarende treet ser slik ut: (NB! uten tag'ene i Scheme-representasjonen) {A B C D E F G H} 17 Mens hvert tre har en tegnmengde, / \ har hver bladnode ett tegn ikke A 8 {B C D E F G H} 9 en singleton tegnmengde. / \ {B C D} 5 {E F G H} 4 / \ / \ B 3 {C D} 2 {E F} 2 {G H} 2 / \ / \ / \ C 1 D 1 E 1 F 1 G 1 H 1 304

Koding For koding av et tegn følger vi de nodene som inneholder tegnet, inntil vi kommer til den aktuelle bladnoden. For hver venstregren vi følger, legger vi et 0-bit til koden, og for hver høyregren legger vi til et 1-bit. F.eks. for å kode C, ser vi fra rotnoden at C ligger i høyre subtre. Sett fra dettes rot, finner vi C i venstre subtre, sett fra dettes rot, finner vi C i høyre subtre, og endelig, sett fra dettes rot, finner vi C i venstre bladnode. Veien høyre venstre høyre venstre gir 1010, som blir koden til C. Avkoding For å avkode fra en bitsekvens til et tegn følger vi grenen på tilsvarende måte. F.eks. med sekvensen 1100 følger vi veien høyre høyre venstre venstre som bringer oss til bladnoden med tegnet E. 305

Generering av Huffman-tre Algoritmen for å generere treet er litt snedig. Den krever ikke all verdens kode, men en smule konsentrasjon. NB! datasekvensen på side 164. i læreboken kan virke noe forvirrende, ettersom parene i hver linje er ordnet etter synkende vekt, Poenget er å få satt opp treet slik at mens algoritmen forutsetter stigende vekt. - de mest brukte symbolene, altså de med høyes vekt, er mest tilgjengelig, hvilket vil si det samme som at - jo mindre brukt et symbol er, desto lengre fra roten ligger det. 306

Vi starter med ett flatt tre, dvs. en liste der - alle symbolene er paret med hver sin vekt, i form av bladnoder - forener bladnoder til to-tegns subtrær og forener to og to subtrær til større subtrær - inntil vi står igjen med ett fullvokst tre. For hver runde - forener vi de to gjenværende bladene og/eller subtrærne A og B som har lavest vekt, - i et subtre med en rotnode C som får en vekt lik summen av vektene til A og B, hvilket innebærer at C, får høyere vekt enn, og blir liggende over, både A og B. Dermed vil - stadig flere bladnoder bli inkorporert i trær, og - stadig flere subtrær bli inkorporert i overordnede trær, inntil vi til slutt står igjen med - en rotnode der - alle subtrær med underligggende subtrær og blader er forenet - og der alle subtrær har lavere vekt enn sine supertrær. NB! Her og i det følgende brukes termen node mer eller mindre synonymt med termen subtre. 307

I eksemplet på neste side vises de suksessive argumentene til prosedyren successive-merge, som binder algoritmen sammen. De blå nodene er de som vil bli slått sammen i løpende runde, mens rammene inneholder det akkumulerte resultatet av sammenslåingene. Legg merke til at bladnodene i den opprinnelige listen er ordnet etter stigende vekt, og at de nye noder som skapes gjennom sammenslåingene også ordnes etter stigende vekt etter de nodene som har samme vekt eller lavere og foran de nodene som har høyere vekt. Og siden vi i hver runde slår sammen de to første, letteste, nodene, slik at disse ved neste sammenslåing havner under noder med høyere vekt, får vi et binært tre ordnet fra bladene mot roten etter stigende vekt. Også her er bladnodene vist uten tag'er, så vi kan se på disse som kjennetegnet ved at første element ikke er et par noe vi forsåvidt også kunne ha gjort i implementasjonen. 308

(successive-merge ; 1 ((h 1) (g 1) (f 1) (e 1) (d 1) (c 1) (b 3) (a 8))) (successive-merge ; 2 ((f 1) (e 1) (d 1) (c 1) ((h 1) (g 1) (h g) 2) (b 3) (a 8))) (successive-merge ; 3 ((d 1) (c 1) ((h 1) (g 1) (h g) 2) ((f 1) (e 1) (f e) 2) (b 3) (a 8))) (successive-merge ; 4 (((h 1) (g 1) (h g) 2) ((f 1) (e 1) (f e) 2) ((d 1) (c 1) (d c) 2)(b 3) (a 8))) (successive-merge ; 5 (((d 1) (c 1) (d c) 2) (b 3) (((h 1) (g 1) (h g) 2) ((f 1) (e 1) (f e) 2) (h g f e) 4) (a 8))) (successive-merge ; 6 ((((h 1) (g 1) (h g) 2) ((f 1) (e 1) (f e) 2) (h g f e) 4) (((d 1) (c 1) (d c) 2) (b 3) (d c b) 5) (a 8))) (successive-merge ; 7 ((a 8) ((((h 1) (g 1) (h g) 2) ((f 1) (e 1) (f e) 2) (h g f e) 4) (((d 1) (c 1) (d c) 2) (b 3) (d c b) 5) (h g f e d c b) 9) )) (successive-merge ; 8 ((((a 8) ((((h 1) (g 1) (h g) 2) ((f 1) (e 1) (f e) 2) (h g f e) 4) (((d 1) (c 1) (d c) 2) (b 3) (d c b) 5) (h g f e d c b) 9) (a h g f e d c b) 17))) 309

Scheme-representasjon (define (make-leaf symbol weight) ; "tag" bladnoden for typeidentifikasjon (list 'leaf symbol weight)) ; sml derivasjonseksemplet i avsnitt 2.3. (define (leaf? object) (eq? (car object) 'leaf)) (define (symbol-leaf x) (cadr x)) (define (weight-leaf x) (caddr x)) (define (make-code-tree left right) (list left ; venstre subtre right ; høyre subtre (append (symbols left) (symbols right)) ; alle symboler i det nye subtreet (+ (weight left) (weight right)))) ; det nye subtreets samlede vekt (define (left-branch tree) (car tree)) (define (right-branch tree) (cadr tree)) (define (symbols tree) (if (leaf? tree) (list (symbol-leaf tree)) ; Mens hvert tre har tegnmengde (caddr tree))) ; har hver bladnode et enkelt tegn (define (weight tree) (if (leaf? tree) (weight-leaf tree) (cadddr tree))) 310

;; adjoin-set plasserer en ny node, et nytt subtre, i listen med subtrær på løpende nivå. (define (adjoin-set x set) ; Plasser x i henhold til stigende vekt (cond ((null? set) ; x er tyngre enn alle andre subtrær, så (list x)) ; plasser x sist ((< (weight x) (weight (car set))) ; Alle trær heretter er tyngre enn x, så (cons x set)) ; plasser x her. (else ; Løpende subtre er like lett som eller lettere en x, så (cons (car set) ; kopier inn løpende node, og (adjoin-set x (cdr set)))))) ; let videre (define (make-leaf-set pairs) ; Konverter en liste med symbol-vekt-par ; til en liste med tag'ede blader (if (null? pairs) '() (let ((pair (car pairs))) ; første gjenværende symbol-vekt-par (adjoin-set (make-leaf (car pair) (cadr pair)) ; put nytt tag'et blad (make-leaf-set (cdr pairs)))))) ; i konvertert liste (define (successive-merge leaf-set)...) ; UKEOPPGAVE (define (generate-huffman-tree pairs) (successive-merge (make-leaf-set pairs))) 311

(define (encode-symbol sym tree)...) ; UKEOPPGAVE (define (encode message tree) (if (null? message) '() (append (encode-symbol (car message) tree) (encode (cdr message) tree)))) (define (decode bits tree) (define (decode-1 bits cur-branch) (if (null? bits) '() (let ((next-branch (choose-branch (car bits) cur-branch))) (if (leaf? next-branch) (cons (symbol-leaf next-branch) (decode-1 (cdr bits) tree)) (decode-1 (cdr bits) next-branch))))) (decode-1 bits tree)) (define (choose-branch bit branch) (cond ((= bit 0) (left-branch branch)) ((= bit 1) (right-branch branch)) (else (error "bad bit -- CHOOSE-BRANCH" bit)))) 312

Imperativ tilstandsendrende, verditilordnende, mutativ, destruktiv programmering Fra en platonsk posisjon kunne vi si noe sånt som dette: Ved beregningen av en funksjons verdi, når alle variabler er bundet, og verden står stille, er beregningen egentlig bare en avdekning av evig og uforanderlig relasjoner mellom tall. Når vi bruker hjelpemidler som småsten, kuleramme, penn og papir eller datamaskiner, der vi hele tiden endrer de sanselige tingenes tilstand, øver vi vold på de idéelle tingenes tilstand. Fra denne posisjonen vil man kunne betegne verditilordning, som destruktiv, og det gjør man da også. En annen ting er at vi i den sannselige materiell verden, bruker materien til langt mer enn regning. Vi spiser, sover, arbeider, sloss, parrer oss, morer oss, osv. og alt dette er fundamentalt sett endring. For noen av disse formål har vi utviklet stadig mer avansert teknologi, og teknologi består i sitt vesen i tilstandsendring, på samme måte som det menneskelige liv består i endring. Altså: En datamaskin er til syvende og sist ikke interessant for oss, med mindre den gjør noe, men for å få gjort mer enn å rotere, blinke og tute, må datamaskinen kunne regne. 313

Hvorfor ta opp vertitilordning og tilstandsendring i et kurs om funksjonell programmering? a. Vi må ha mer enn en overflatisk kjenneskap til forskjellen. Paradigmet for imperativ programmering er iterasjon med verditilordning. Paradigmet for funksjonell programmering er rekursjon med argument passing. b. Visse operasjoner er svært tungvinte, eller kanskje umulige, hvis vi insisterer på funksjonell programmering på alle implementasjonsnivåer. Også språk som på høyeste nivå er strengt funksjonelle, bruker tilstandsendringer på lavere nivåer, nettopp for å få til slike operasjoner. c. Visse funksjonelle objekter bærer med seg essentiell intern tilstandsinformasjon. Dette gjelder bl.a. tabeller for lagring av ferdig utregnede resultater (memoisering) og parene i det vi kaller strømmer, en form for (potensielt) uendelige lister der hvert objekt er sin egen funksjon. (litt uklart dette nå, men blir klarere etter hvert) 314

EKSEMPEL 1 INPUT Input til et program kommer utenfra, fra en verden programmet ikke har kontroll over, og er sitt vesen tilstandsbasert, ettersom det bringer inn verdier som ikke kune forutsees (beregnes) idet programmet startet. I språket Haskell brukes monader bl.a. for å håndtere IO. Begrepet monade er vanskelig. 315

EKSEMPEL 2 VILKÅRLIGE TALL Vilkårlighet generering av random-tall, er også essensielt tilstandsbasert. Riktignok men kan man ikke programmere vilkårlighet man kan programmere pseudo-vilkårlighet, noe som for mange formål er godt nok. Programmert vilkårlighet er en kontradiksjon. En fornuftig (pseudo-)random-generator vil kjenne sin egen tilstand, og holde denne for seg selv. Alternativt, i et strengt funksjonelt program, ville random-generatoren selv, og de variabler den brukte for generering av nye tall, måtte sendes rundt til alle de prosedyrer som direkte eller indirekte brukte den. 316

En random-generator er i realiteten en rekursiv funksjon r med en tilhørende oppdateringsfunksjon f, slik at r(x i+1 ) = f(r(x i ) ). Eller, sagt på en annen måte: En random-generator er i realiteten en sekvens X 0, X 1, X 2, med en tilhørende oppdateringsfunksjon f, slik at X k+1 = f(x k ). I et strengt funksjonelt program må vi sende f og X k rundt omkring, fra den ene berørte prosedyren til den andre. (Vi ser mer på random-tall i neste forelesning.) Men allikevel: Enhver fornuftige randomgenerator er modulus-basert. Dvs. vi definerer f ved bl.a. en m, slik at 0 X k < m og 0 f(x k ) < m, for alle k 0. Dermed kan sekvensen inneholde maks m ulike tall, og det lar seg vise at det da må finnes et tall p slik at X k = X k+p, for alle k 0, spesifikt slik at X 0 = X p, hvilket betyr at sekvensen repeteres for hvert p-te tall. Vi sier at X 0 X p er sekvensens periode og at p er sekvensens periodelengde. Vi kunne dermed i prinsippet ha representert enhver fornuftig randomgenerator ved en endelig liste, men i praksis ville dette ha krevd for mye plass. (Et ekstremt tilfelle er Multiply With Carry som kan ha periodelengder på opp i mot 2 2000000 (et tall med over syv hundre tusen desimale sifre langt, langt mer enn antall atomer i universet, som er 10 80 ). Her er f definert ved en mengde av tilstandsvariabler, slik at f(x k ) er bestemt av både X og k.) 317

Scheme har følgende prosedyrer for verditilordning set! uttalt sett bæng (set! a b) binder variabelen a til verdien b, til fortrengsel for den verdien a hadde. set-car! uttalt sett kar bæng (set-car! a b) binder car-delen i paret a til verdien b. til fortrengsel for den verdien (car a) hadde. set-cdr! uttalt sett kuddr bæng (set-cdr! a b) binder cdr-delen i paret a til verdien b. til fortrengsel for den verdien (cdr a) hadde. I tillegg finnes de destruktive prosedyrene vector-set! og string-set! Særlig bruken av set-cdr! kan gi opphav til noen nokså tricky situasjoner, som når to lister løper sammen, eller en liste løper sammen med seg selv. 318

Et par hvis innhold kan endres ved verditilordning, kalles et muterbart par. I læreboka, og i R5RS, er alle par muterbare, mens det i Racket skilles mellom ikke-muterbare og muterbare par, slik at disse er to forskjellig typer, distingvert ved at alle prosedyrer for muterbare par er prefikset med m mpair?, mcons, mcar, mcdr, mlist, etc. I forelesningene holder vi oss til læreboka. For å løse oppgaver med muterbare par, kan man velge R5RS som språk i DrRacket (anbefales), eller bruke Racket sine muterbare par. For de det måtte interessere: Rackets har rutinebiblioteker som er utilgjengelig fra R5RS, for sortering, filtrering o.l., og for behandling av filer og directories. 319

Vi har et kar (en veskebeholder), representert i Scheme ved et par der car-delen angir kapasiteten og cdr-delen angir det aktuelle innholdet, dvs. volumet til vesken i karet. Her er to implementasjoner av tapping / påfylling av beholderene funksjonell imperativ. (define (tapp-eller-fyll beholder delta) (let ((kapasitet (car beholder)) (innhold (+ (cdr beholder) delta))) (cond ((< innhold 0) ; prøver å tappe mer ; enn det er igjen (cons kapasitet 0)) ((> innhold kapasitet); prøver å fylle på mer ; enn det er plass til (cons kapasitet kapasitet)) (else (cons kapasitet innhold))))) (define (tapp-eller-fyll! beholder delta) (let ((kapasitet (car beholder)) (innhold (+ (cdr beholder) delta))) (cond ((< innhold 0) ; prøver å tappe mer ; enn det er igjen (set-cdr! beholder 0)) ((> innhold kapasitet) ; prøver å fylle på mer ; enn det er plass til (set-cdr! beholder kapasitet)) (else (set-cdr! beholder innhold))))) Her får vi tilbake et nytt kar, Her endrer vi innholdet i det opprinnelige karet. med summen av eller differansen mellom opprinnelig og påfylt eller tappet veske. 320

Til dette bildet hører det et nytt syn på forholdet mellom en variabel og dens verdi: Det er ikke lenger snakk om en enkel, umiddelbar binding mellom variabel og verdi, men om en relasjon mellom - en variabel og - et sted med - plass til - en verdi. (Andre termer er lokasjon, beholder eller beholder i en lokasjon.) 321

Setningssekvensen (define v 4) (set! v 266) gir opphav til en tilstandssekvens som kan visualisere på ulike måter: Peker til beholder v 4 v 266 Figur 1 v er bundet til en peker til en beholder. Adresse i RAM v: 7 0:06B32A90 v: 7 0:06B32A90 1:094356BF 1:094356BF 2:654BF95D 2:654BF95D 3:A5E5431B 3:A5E5431B 4:0800A20A 4:0800A20A 5:00025F0E 5:00025F0E 6:FF56CB88 6:FF56CB88 7:00000004 7:0000010A 8:0000A500 8:0000A500 9:65498E5A 9:65498E5A A:0266F6A5 A:0266F6A5 Figur 2 v er bundet til lokasjon 7. Uansett visualiseringsmåte er poenget at - variabelen bevarer sin identitet uavhengig av sin verdi, i kraft av at - den ikke refererer direkte til en verdi, men - til noe som kan inneholde en verdi, men som dermed også - kan få sitt innhold endret. (Verdiene er angitt heksadesimalt, slik at f.eks. 10A 16 = 266 10 og 25F0E 16 = 155406 10. De heksadesimale sifrene er 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.) 322

Et eksempel på grov misbruk av verdittilordning 1. (define a 3) 2. (define (make-proc b) (lambda (c) (+ a b c))) 3. (define p (make-proc 5)) 4. (p 7) ; ==> 15 5. (set! a 8) 6. (p 7) ; ==> 20 7. (set! p (lambda (d) 'who-what-where?)) 8. (p 7) ; ==> 'who-what-where? - Etter 1. er a er bundet til 3 i den globale omgivelse G. - Etter 3. har prosedyren p en umiddelbar omgivelse A, beskrevet i 2, der b er bundet til 5, med G som superomgivelse. - For utførelsen av kallet i 4 opprettes det en omgivelse B, der er c er bundet til 7, med A som superomgivelse. - For utførelsen av kallet i 6 opprettes det en omgivelse C, der er c er bundet til 7, med A som superomgivelse. - Men pga. endringen av verdien til a i 5, gir kallene i linje 4 og 6 ulike resultater, selv om p kalles med de samme variablene som argumenter. - I 7 destrueres den opprinnelige p, og A mister sin referanse og blir til søppel. - I 8 opprettes en omgivelse D der d er bundet til 7, men siden koden til p nå er endret, får vi nok et annet resultat. 323

Er det stor avstand mellom de ulike kallene på p og mellom kallene på p og endringer av verdien til a, får vi programmer der tilstandsendringene er vanskelig å overskue. For å unngå dette, (a) (b) må enhver muterbar variabel tilhøre en prosedyre og ligge i dennes lokale omgivelser, og alle globale variabler må være konstanter som f.eks. og e, eller ikke-muterbare prosedyrer typisk primitiver. 324

Modularitet, objekter og tilstander SICP 3 En bankkonto og dens saldo Anta vi har en bankkonto med en startkapital som vi har belastet med en serie uttak. Vi har notert uttakene i en liste og kjører nå denne mot startkapitalen for å finne saldoen. (define (beregn-saldo saldo uttaksliste) (if (null? uttaksliste) saldo (beregn-saldo (- saldo (car uttaksliste)) (cdr uttaksliste)))) (beregn-saldo 120 '(20 40 30)) 30 (beregn-saldo 120 '(20 40 30 20 30 40 20)) -80 325

Med en mer forsiktig tilnærming, kunne vi sette vi opp en liste over planlagte uttak, og sjekke hvor mange av disse vi kan realisere før saldoen går under null. (define (mulige-uttak saldo uttak) (if (or (null? uttak) (< (- saldo (car uttak)) 0)) '() (cons (car uttak) (mulige-uttak (- saldo (car uttak)) (cdr uttak))))) (mulige-uttak 120 '(20 40 30 20 30 40 20)) (20 40 30 20) 326

Endring og identitet Den funksjonelle løsningen er deterministisk, i likhet med alle funksjonelle beregnnger, og lite egnet for administrasjon av bankkonti. Det vi trenger er et system som holder styr på endringene i vår bankkonto over tid. og til dette trenger vi språklige mekanismer som tar vare på at et objekt forblir ett og det samme samtidig som det endres. Først prøver vi med en globale variabel, uten å endre dens verdi. Eksempel 1. (define saldo 100) (define (uttak beløp) (- saldo beløp)) (uttak 60) 40 (uttak 60) 40 ; som om vi ikke hadde tatt ut noe tidligere. 327

Dette er åpenbart ikke det vi ønsker, så vi tar i bruk den destruktive operasjonen set! Eksempel 2. (define saldo 100) (define (uttak! beløp) (set! saldo (- saldo beløp)) saldo) (uttak 60) 40 (uttak 60) -20 Og, la oss, før vi går videre, forbedre prosedyren slik at den ikke tillater overtrekk. Eksempel 3 (define saldo 100) (define (uttak beløp) (if (>= (- saldo beløp) 0) (begin (set! saldo (- saldo beløp)) saldo) "Uttaket mangler dekning")) (uttak 60) 40 (uttak 60) "Uttaket mangler dekning" 328

Sårbarhet og uoversiktlighet Problemet med de tre ovenstående løsningene er at de gjør programmet både sårbart og uoversiktlig. Den forbedringen som ligger i Eksempel 3, hjelper ikke mot sårbarheten, ettersom saldo er en global variabel som endres, og den gjør helle ikke programmet mer oversiktlig, ettersom saldo kan endres hvor som helst. Det vi trenger er en eksklusivt kobling mellom saldoen og uttaksprosedyren, slik at uvedkommende prosedyrer ikke kan forgriper seg på kontoen, og forbindelsen mellom saldoen og uttaksprosedyren blir åpenbar. 329

En måte å knytte saldoen til prodedyren på, er å pakke prosedyren inn i et lokalt let-uttrykk. Eksempel 4. (define konto (let ((saldo 100)) (lambda (beløp) (if (>= (- saldo beløp) 0) (begin (set! saldo (- saldo beløp)) saldo) "Uttaket mangler dekning")))) (konto 60) 40 (konto 60) "Uttaket mangler dekning" Poenget er at så lenge prosedyren uttak eksisterer, så eksisterer også dens lokale definisjonsomgivelse. Saldoen kan bare aksesseres, og evt. endres, innefor denne omgivelsen, og forbindelsen mellom saldoen og uttaksprosedyren er tydelig. Bortsett fra verdiendringen er det ingen prinsippiell forskjell på Eks. 4 og dette: (define foo (let ((bar 2)) (lambda (x) (* bar x)))) (foo 7) 14 330

Videreutvikling av bankkontoprosedyren Det er en åpenbar praktisk begrensning ved Eksempel 4, idet kontoen uttak er den enste som finnes. Dette fikser vi ved å definer en konstruktor, og, for å unngå at alle konti starter med en saldo på kr 100, lar vi saldoen være argument til konstruktoren. Den får da plass i prosedyrens lokale omgivelser, helt på linje med saldoen i let-uttrykket. Eksempel 5. (define (lag-konto saldo) (lambda (beløp) (if (>= (- saldo beløp) 0) (begin (set! saldo (- saldo beløp)) saldo) "Uttaket mangler dekning"))) (define konto (lag-konto 100)) (konto 60) ; => 40 (konto 60) ; => "Uttaket mangler dekning" 331

Det er stadig slik at så lenge prosedyren konto finnes, så finnes også dens omgivelser. Ved hjelp av lag-konto, kan vi nå lage ikke bare én, men så mange konti vi vil. (define konto-1 (lag-konto 100)) (define konto-2 (lag-konto 100)) (konto-1 40) ; => 60 (konto-2 60) ; => 40 (konto-1 50) ; => 10 (konto-2 50) ; => "Uttaket mangler dekning" Selv om konto-1 og konto-2 begge tilfeldigvis fikk samme startkapital = 100, er de to forskjellige objekter, og etter at det er gjort ett uttak fra konto-1 og konto-2 på hhv. 40 og 60 har de forskjellige saldi. 332

Videreutvikling av bankkontoprosedyren Vi kan utvide kontoens handlingsreportoir: Eksempel 6 (define (lag-konto saldo) (define (uttak beløp) (if (>= (- saldo beløp) 0) (begin (set! saldo (- saldo beløp)) saldo) "Uttaket mangler dekning")) (define (innskudd beløp) (set! saldo (+ saldo beløp)) saldo) (define (ekspedér beskjed) (cond ((eq? beskjed 'uttak) uttak) ((eq? beskjed 'innskudd) innskudd) (else (error "Ukjent beskjed -- LAG-KONTO" beskjed)))) ekspedér) Her får vi, i stedet for en enkel uttaksprosedyre, en prosedyre som håndterer både uttak og innskudd, i henhold til ulike beskjed-argumenter 333

(define konto (lag-konto 100)) ((konto 'uttak) 60) ; => 40 ((konto 'uttak) 60) ; => "Uttaket mangler dekning" ((konto 'innskudd) 70) ; => 110 ((konto 'uttak) 60) ; => 50 ((konto 'saldo) 60) ; gir kjøreavbrudd med feilmeldingen : Ukjent beskjed -- LAG-KONTO saldo Vi kunne selvsagt ha hatt en cond-clause i ekspedér som behandlet meldingen Her opptrer 'saldo, men det har vi altså ikke. prosedyrene uttak, innskudd og ekspedér og parameteren saldo i de samme lokale omgivelsene, og et kall på lag-konto returnerer en instans av prosedyren ekspedér i disse omgivelsene med saldo bundet til verdien til det aktuelle argumentet til lag-konto. 334

335

336