INF2810: Funksjonell Programmering Dataabstraksjon og Trerekursjon Stephan Oepen & Erik Velldal Universitetet i Oslo 15. februar, 2013
Tema 2 Forrige uke Høyere-ordens prosedyrer: Prosedyrer som argumenter Prosedyrer som returverdi Lokale Variabler I dag Noe repetisjon Dataabstraksjon Lister av lister Trerekursjon
reduce 3 En annen klassisk høyere-ordens prosedyre (ved siden av map):? (define (reduce proc init items) (if (null? items) init (proc (car items) (reduce proc init (cdr items)))))? (reduce + 0 (1 2 3 4 5)) 15? (reduce * 1 (1 2 3 4 5)) 120? (reduce cons () (1 2 3 4 5)) (1 2 3 4 5)? (reduce max 0 (1 2 3 4 5)) 5 reduce er også kjent som fold, compress, accumulate, eller inject.
Eksempel på bruk av map + reduce 4 Gitt to vektorer a = 2, 1, 3 b = 1, 3, 0 (define a (list 2 1 3)) (define b (list 1 3 0)) så beregnes prikk-produktet som x y = i x i y i a b = 2 1 + 1 3 + 3 0 = 5 Versjon 2 med modulær design: kjede av høyere-ordens operasjoner over sekvenser. sekvenser som konvensjonell grensesnitt i SICP-terminologi. (define (dot-product x y) (if (null? x) 0 (+ (* (car x) (car y)) (dot-product (cdr x) (cdr y))))) (define (dot-product x y) (reduce + 0 (map * x y))) (dot-product a b) 5
lambda-uttrykk 5? (define square (lambda (x) (* x x)))? (square 4) 16? ((lambda (x) (* x x))) 4) 16? (map (lambda (x) (* x 0.1)) (2 3 4)) (0.2 0.3 0.4)? (map (lambda (x) (list x (* x x))) (2 3 4)) ((2 4) (3 9) (4 16)) lambda-uttrykk returnerer prosedyrer. Trenger ikke nødvendigvis bindes til et symbol; Kan kalles direkte som såkalt anonyme prosedyrer. Brukes ofte i forbindelse med høyere-ordens prosedyrer. Terminologien avslører røttene ved Alonzo Church (på 30-tallet).
Prosedyrer inn, prosedyrer ut 6 Parametriserbar søk-og-erstatt med anonyme høyere-ordens prosedyrer:? (define (make-replacer pred proc) (lambda (z) (if (pred z) (proc z) z)))? (map (make-replacer odd? (lambda (x) (+ 1 x))) (1 42 2 42 3)) (2 42 2 42 4)
Lokale variabler og lambda 7 Formål: normalisere sekvens av tall som prosentandel av summen.? (percentages (10 20 30 40 50)) (6 2 3 13 1 3 20 26 2 3 33 1 3 ) (define (percentages items) (map (lambda (x) (* 100 (/ x (reduce + 0 items)))) items)) (define (percentages items) (define (helper sum) (map (lambda (x) (* 100 (/ x sum))) items)) (helper (reduce + 0 items))) (define (percentages items) ((lambda (sum) (map (lambda (x) (* 100 (/ x sum))) items)) (reduce + 0 items))) Parameteret til den indre prosedyre fungerer som en lokal variable.
Lokale variabler, lambda, og let 8 Bruk av lokale variabler er så vanlig at det finnes en kortform: let. (define (percentages items) ((lambda (sum) (map (lambda (x) (* 100 (/ x sum))) items)) (reduce + 0 items))) (define (percentages items) (let ((sum (reduce + 0 items))) (map (lambda (x) (* 100 (/ x sum)) items)))) let er å regne som syntaktisk sukker i Scheme (gir økt lesbarhet). Navnet gjenspeiler matematisk språkbruk ( Let a be a vector... ).
Lokale variabler, lambda, og let 9 Generell form for let Ekvivalent lambda-uttrykk (let (( var1 exp1 ) ( var2 exp2 ). ( varn expn )) body ) ((lambda ( var1 var2... varn ) body ) exp1 exp2... expn ) Rekkevidden til variablene er kroppen til let-uttrykket. Verdiene ( exp 1... exp n ) beregnes utenfor let-uttrykket. Variablene har ikke tilgang til hverandre under bindingen. let* som kortform for flere omsluttende let-uttrykk.
Noen eksempler med let? (define foo 42)? (let ((x foo) (y 1)) (list x y)) (42 1)? (let ((x foo) (y foo)) (list x y)) (42 42)? (let ((x foo) (y x)) (list x y)) error: x undefined? (let ((foo 7) (y foo)) (list foo y)) (7 42)? (let ((foo 7)) (let ((y foo)) (list foo y))) (7 7)? (let* ((foo 7) (y foo)) (list foo y)) (7 7) 10
Dataabstraksjon 11 Så langt: modularisering og abstraksjon av prosesser (beregninger). Like viktig er modularisering og abstraksjon av data ( kunnskap ). Hvilke typer data kjenner vi (i Scheme)? tall integer, real, rational 42, 3.1415, 2/3 sekvens av tegn string "foo bar" sannhetsverdi boolean #t, #f par (2-tuple) pair (cons) (47. 11) tom sekvens null () funksjoner prosedyre (lambda) +, (lambda (x) (* x x)) Skille mellom abstrakte datatyper og deres implementasjon i Scheme.
Repetisjon: Lister 12 Kjeder av cons-par der siste elementet er den tomme lista; ().? (cons 1 (cons 2 (cons 3 (cons 4 ()))))) (1 2 3 4) Lister kan defineres rekursivt som: () eller et par der cdr er en liste.
Lister som vår første komplekse datatype 13 (define (list? object) (or (null? object) (and (pair? object) (list? (cdr object)))))? (list? (list 1 2 3 4)) #t? (list? (1)) #t? (list? (cons 1 ())) #t? (list? ()) #t? (list? 1) #f? (list? (cons 1 2)) #f
Dataabstraksjon og komplekse data 14 Ofte behov for å binde sammen en gruppe sammenhengende data. Komplekse datatyper grupperer informasjon som konseptuelt hører sammen (compound data), f.eks. rasjonale tall: 1/3. Dataabstraksjon skjuler en datatypes intern representasjon. Familie av prosedyrer som grensesnitt: constructor, selectors, predicate. (define (make-rat n d) (cons n d)) (define (rat-numer r) (car r)) (define (rat-denom r) (cdr r)) (define (rat? r) (pair? r)) Her bruker vi cons for å lime sammen to heltall: teller og nevner. Nok for å definere prosedyrer som konseptuelt regner på rasjonale tall.
Aritmetikk med rasjonale tall 15 a b an a d a+b an a d bn b d = + bn b d an bn a d b n = an b d + b n a d a d b n Operasjoner på rasjonale tall er uavhengig av intern representasjon. Abstraksjonsbarriere: dataabstraksjon skjuler limet (cons-cellen i bunnen). Closure property: limet kan brukes på ting som selv er blitt limet sammen. Hvilken type lim bruker vi i Scheme? Holder oss til bare cons i flere uker. (define (mul-rat a b) (make-rat (* (rat-numer a) (rat-numer b)) (* (rat-denom a) (rat-denom b)))) (define (add-rat a b) (make-rat (+ (* (rat-numer a) (rat-denom b)) (* (rat-numer b) (rat-denom a))) (* (rat-denom a) (rat-denom b))))...
På sidespor: Blanding av datatyper 16 1 + 1 3 = 4 3 3 3 + 1 3 = 4 3 Scheme gjør det enkelt å definere såkalte polymorfe prosedyrer. Bunnoperasjonene avgjør hvordan kombinere forskjellige typer data. (define (add-rat a b) (let ((a (if (integer? a) (make-rat a 1) a)) (b (if (integer? b) (make-rat b 1) b))) (make-rat (+ (* (rat-numer a) (rat-denom b)) (* (rat-numer b) (rat-denom a))) (* (rat-denom a) (rat-denom b)))))? (add-rat 1 (make-rat 1 3)) (4. 3) Inklusjon av datatyper: integer rational automatisk coercion.
Nok en gang: Lister 17 Kjeder av cons-par der siste elementet er den tomme lista; ().? (cons 1 (cons 2 (cons 3 (cons 4 ()))))) (1 2 3 4) closure property: vi kan bygge komplekse strukturer fra bunnen av.
Prosedyrer vs. data 18 Husk at prosedyrer i Scheme er førsteklasses objekter, altså data; f.eks. som argument eller returverdi; Scheme-koden skrives som lister. Prosedyrer kan også brukes som lim til komplekse data, istedenfor cons. (define (cons x y) (lambda (message) (cond ((= message 0) x) ((= message 1) y)))) (define (car proc) (proc 0)) (define (cdr proc) (proc 1))? (cons 1 2)) #<procedure>
Hjelp! Hva skjer? 19 Et par kan bindes sammen som en prosedyre som returnerer enten car-verdien eller cdr-verdien, avhengig av hvilken beskjed den får. Så implementeres car og cdr som oppkalling av cons-prosedyren. Ved denne oppkallingen sender car en beskjed som velger ut riktig. Mindre effisient representasjon enn cons-celle (dvs. par av to pekere). Konseptuelt interessant: message passing; dette kommer vi tilbake til. (define (cons x y) (lambda (proc) (proc x y))) (define (car proc) (proc (lambda (p q) p)))
Lister som hierarkiske strukturer 20 Hvert liste-element kan selv være en liste... Som igjen kan bestå av nye lister.? (cons (list 1 2) (list 3 4)) ((1 2) 3 4)
Lister som trær 21 Lister av lister kan sees som trær: Hvert element i en liste er en gren. Elementer som selv er lister er subtrær. Løvnodene i treet er de atomære elementene som ikke er lister. NB: så langt kan vi bare ha verdier på løvnodene, og vi skal etterhvert lage en generalisert implementasjon av trær.
Rekursjon på lister av lister 22 Må passe på at rekursjonen går ned i hver (element)liste. For eksempel: telle løvnoder (parallelt til length på sekvenser). Må tenke på tre forskjellige situasjoner; Argumentet kan være: den tomme lista en cons-celle (et tre) noe annet (define (count-leaves tree) (cond ((null? tree) 0) ((pair? tree) (+ (count-leaves (car tree)) (count-leaves (cdr tree)))) (else 1)))? (count-leaves ((1 2) 3 4)) 4
Rekursjon på lister av lister (forts.) 23 For eksempel: samle opp løvnoder i en flat list (fringe eller flatten. Må tenke på tre forskjellige situasjoner; Argumentet kan være: den tomme lista en cons-celle (et tre) noe annet (define (fringe tree) (cond ((null? tree) ()) ((pair? tree) (append (fringe (car tree)) (fringe (cdr tree)))) (else (list tree))))? (fringe ((1 2) 3 4)) (1 2 3 4)
Rekursjon på lister av lister 24 map er definert for lister; tree-map kan defineres for lister av lister: (define (tree-map proc tree) (cond ((null? tree) ()) ((pair? tree) (cons (tree-map proc (car tree)) (tree-map proc (cdr tree)))) (else (proc tree))))? (tree-map square ((1 2) 3 4)) ((1 4) 9 16) Alternativt kan map brukes, i kombinasjon med rekursjon: (define (tree-map proc tree) (map (lambda (subtree) (if (pair? subtree) (tree-map proc subtree) (proc subtree))) tree)) Hvorfor trengs det bare én rekursiv oppkalling i denne varianten?
På sidespor: dotted pairs vs. lister 25 Hvordan Scheme viser cons-celler:? (cons 1 2) (1. 2)? (cons 1 (cons 2 ())) (1 2)? (cons (list 1 2) 3) ((1 2). 3) I bunnen er (1 2 3) en cons-kjede (1. (2. (3. ()))); Er cdr-verdien et par sløyfer Scheme punktum og parentes:. (.? (1. (2. (3. ()))) (1 2 3) Dermed kan vi skrive prosedyrer som tar et variabel antall argumenter: (define (map fn. lists)...)