FP vs OOP, del 1 Funksjonell programmering 

advertisement
Asbjørn Brændeland hos Logica 1. mrs 2012
FP vs OOP, del 1
Funksjonell programmering

recursion

closure

lambda () calculus

local state

first class objekts

lazy evaluation

currying

delayed evaluation

referential transparency

memoizing

immutability

streams

continuations
1
Lisp‐dialekten Scheme Scheme er den ene av de to store Lisp‐dialektene. S‐expressions og prefix‐notasjon Den andre er Common Lisp. Et prosedyrekall består av Her brukes Scheme i eksemplene. en parentes med ett eller flere uttrykk. Alle uttrykkene evalueres og resultatet av evalueringen av det første anvendes på resultatene av evalueringen av de øvrige. (+ 7 3) ==> 10 Regelen er rekursiv (+ (+ 1 6) (– 12 9)) ==> (+ 7 3) ==> 10 A ==> B leses "A evaluerer til B". 2
Recursion n! (uttales "n fakultet") = 123 … n.
Hvis n = 1, returnér 1, hvis ikke returnér n  (n – 1)! immediate implementation tail recursive implementation
(define (fac n) (if (= n 1) 1 (* n (fac (‐ n 1))))) (define (fac n r) (if (= n 1) r (fac (‐ n 1) (* r n) (fac 5) (fac 5 1) (* 5 (fac 4)) (fac 4 5) (* 5 (* 4 (fac 3))) (fac 3 20) (* 5 (* 4 (* 3 (fac 2)))) (fac 2 60) (* 5 (* 4 (* 3 (* 2 (fac 1))))) (fac 1 120) (* 5 (* 4 (* 3 (* 2 1)))) ==> 120 (* 5 (* 4 (* 3 2))) Her ligger det rekursive kallet ytterst i prosedyrekroppen (* 5 (* 4 6)) og argumentenes endres i samme takt som variablene i en løkke. (* 5 24) Dette kan interpreteren utnytte, slik at den ==> 120 returnerer fra det siste rekursive kallet direkte til kallstedet for det første kallet. På den måten blir halerekursjon like effektivt som predefinert løkke‐iterasjon i andre språk. 3
 calculus
Et -uttrykk evaluerer til en prosedyre.

x . x+x
en prosedyre som tar ett argument og legger dette til seg selv.
==>
variabel kropp
la
f =  x. x + x.
(define f (lambda (x) (+ x x)))
Da evaluerer f(3) til 6,
(f 3) ==> 6
men det gjør også ( x. x + x)(3) .
((lambda (x) (+ x x)) 3) ==> 6
(define (f x) (+ x x))
(define f (lambda (x) (+ x x)))
er syntaktisk sukker for 4
First Class Objects og Higher Order Procedures -kalkyle gir prosedyrer som opptrer som egne, first class objects — som argumeneter og returverdier. En prosedyre av høyere orden (higher order procedure) tar minst ett prosedyreargument og/eller returnerer en prosedyre. Prosedyren p tar 4 argumenter f, g, a, b, hvorav
f er en unær og g er en binær prosedyre,
anvender g på a og b, og
anvender f på resultatet.
(define p (lambda (f g a b) (f (g a b))))
(p square‐root + 17 8) ==> 5
(p square – 17 8) ==> 81
(p floor (lambda (x y) (/ (+ x y) 2)) 17 8) ==> 12
5
Currying En flerargumentsprosedyre kan skrives om til et nøste av unære prosedyrer (lambda (x y) (+ x y))
kan skrives om til (lambda (x) (lambda (y) (+ x y))) ==> en prosedyre som tar et argument x og returnerer en prosedyre som tar et argument y og legger dette til x (define f (lambda (x) (lambda (y) (+ x y)))) (f 7) ==> (lambda (y) (+ 7 y)) = ((f 7) 4) ==> 11 (define g (f 7)) (g 4) ==> 11
6
en prosedyre som tar et argument y og legger dette til 7 Referential Transparency — vi gjennomskuer alt, intet er skjult for oss Kallet på en prosedyre gir alltid ‐ samme resultat med ‐ samme argument Gitt funksjonene f, så er
f(2) = f(2)
på samme måte som
2 = 2.
Generelt:
Hvis x = y, så er alltid
f(x) = f(y).
Og uansett hvor dypt vi går, hvis r = (lambda (a) <et regnestykke der a inngår>) s = (lambda (a) <et regnestykke der kallet (r a) inngår>) t = (lambda (a) <et regnestykke der kallet (s a) inngår>) x = y så skal (t x) alltid evaluere til det samme som (t y).
7
Immutability Par Byggestenen for sammensatte objekter er par. Lister En liste er et par der første element er en eller annen verdi, og det andre elementet er en liste. Destruksjon Endrer vi verdien til det andre elementet, så endrer vi også listen. Dette kalles en destruktiv operasjon. Et streng funksjonelt språk tillater hverken (a) verditilordning (at variabler tilordnes nye verdier), eller (b) destruktive operasjoner på sammensatte objekter, mens noen språk tillater (a) men ikke (b). Identitet
La O være et objekt, la t og u være to etterfølgende ulike tilstander i O,
og la Ot og Ou være O i henholdsvis tilstand t og tilstand u.
Fra et objektorientert perspektiv er Ot = Ou, men
fra et rent funksjonelt perspektiv er Ot  Ou.
8
Closure — innpakking av én prosedyre i en omgivelse En closure er en førsteklasses prosedyre med frie variabler som er bundet i prosedyrens omgivelse. (lambda (y) (+ x y))
Her er x fri og y bundet.
(lambda (x) (lambda (y) (+ x y)))
Her er både x og y bundet
Må ikke forveksles med closure i abstrakt algebra, der vi f.eks. sier at tallene er lukket under addisjon fordi summen av to tall selv er et tall. (define g (f 7))
Prosedyreobjektet g er nå et objekt bestående av
en omgivelse Omgivelse
der x er bundet,
x=7
og en prosedyrekropp der også y er bundet,
variabel y.
kropp (+ x y)
Closure kan brukes til å skjule lokal tilstand. Closure gir i prinsippet en semantikk for objektorientert programmering. 9
Local State Et rent funksjonelt språk, kan strengt tatt ikke brukes til annet enn beregninger. Vi skal senere se på et par kodeeksempler: må vi tillate tilstandsendringer. ‐ en random generator og en kalkulator Vil vi gjøre mer ut av det, Dette gjelder brukergrensesnittet, IO generelt, styringssystemer for biler, fly industrielle prosesser, osv. Tingen er å skjule tilstandene inne i prosedyreobjekter og det får vi til med closure. Closure fjerner ikke de problemene tilstandsendringer skaper, men det avgrenser dem. Flere språk har mer raffinerte konstruksjoner som gir bedre kontroll, så som Haskell, med sine monader. 10
Lazy Evaluation Argumentoverføringsmodellen Pass by name beregninger utføres bare hvis de er nødvendige bl.a. i Algol og Simula. gir lazy evaluation. —hvis A, utfør B, og hvis ikke, utfør C. Enkle argumenter, som må være variabler, har samme navn og betydning I de fleste språk evalues valgsetninger på denne måten. i og utenfor prosedyren. Et argument kan også være I Scheme kan vi definere egne syntaktiske former et sammensatt uttrykk, som (ofte kalt makroer) som evalueres på denne måten. ikke evalueres før det eventuelt blir nødvendig. Dette kalles en thunk.
Lazy evaluation har den åpenbare fordel at, Andre modeller er en plass‐ og tidkrevende evaluering av et argument Pass by value kan utsettes til den eventuelt blir nødvendig—kanskje aldri. bl.a. i C omgås gjerne for store strukturer ved å sende en peker til strukturen Pass by reference bl.a. i C++ og Scheme, for sammensatte objekter 11
Delayed Evaluation lazy evealuering sammen med et løfte om en fremtidig evaluering av det aktuelle uttrykket, etter behov. Memoizing brukes av prosedyreobjekter for å huske tidligere utførte regnestykker, identifisert ved argumentet eller argumentene. En memoiserende prosedyre som kalles flere ganger med samme argument, vil ved første kall utføre sitt regnestykke og lagre resultatet, og deretter, ved de etterfølgende kall, ganske enkelt hente frem resultatet. Resultatene kan bli tableized, dvs. lagret i en tabell, med samme dimensjoner som prosedyrens aritet. (det antall argumenter prosedyren tar) F.eks. vil en binær prosedyre p bruke en todimensjonal tabell og lagre resultatet av kallet (p a b) i linje index(a), kolonne index(b) i tabellen. 12
Streams er potensielt uendelig ekvenser der evalueringen av de enkelt leddene evalueringen av ett ledd alltid genererer utsettes til det blir behov for dem, og et løfte om evalueringen av ett neste ledd. En vektor (array) er statisk og deklareres med en fast størrelse. En liste er dynamisk, og kan vokse og avta under programmets gang, men vil alltid ha en ende som vi er nødt til å teste for når vi opererer på listen. I en uendelig strøm trenger vi aldri (Det finnes også endelig strømmer) (Også filer er strømmer, men disse betrakter vi på en litt annen måte.) å teste for om strømmen er slutt. En strøm er både en liste og en rekursiv funksjon 13
Erathostenes' sieve Konstruktoren cons‐stream er en spesialform som tar to argumenter a og b og returnerer paret med a som første og et løfte om evalueringen av b som andre element. Selectoren stream‐car tar et strøm‐par og returnerer dettes første element. Selectoren stream‐cdr tar et strøm‐par, avtvinger evalueringen av løftet i paret og returnerer strømmen fra og med det evaluerte løftet. (define (sieve S) a. (cons‐stream (stream‐car S) b. (sieve (stream‐filter (lambda (x) (not (divisible? x (stream‐car S)))) (stream‐cdr S))))) (define primes (sieve (stream‐cdr integers))) a. Konstruer strømmen bestående av første gjenværende tall, her kalt p, i S, fulgt av b. strømmen av de tall i S som ikke har p som faktor. Siden sieve er rekursiv, vil, for hver runde, ingen av de tallene som finnes i løpende S ha noen av de foregående tallene som faktor. Her er alle tallene fra og med 3 til og med 13 evaluert mht. delelighet, men ingen er evaluert mer enn én gang (2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 (3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 (5 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49 (7 11 13 17 19 23 29 31 37 41 43 47 49 (11 13 17 19 23 29 31 41 43 47 (13 17 19 23 29 31 41 43 47 14
Continuations (define (ping pong) (ping pong) (let loop ((n 1)) ping 1 : Can I borrow your computer? (print "ping" n ": Can I borrow your computer? ") You'll get it back right away. OK. (set! pong (call/cc pong)) Thank you, that was it. Your'e welcome. (print "You'll get it back right away. ") ping 2 : Can I borrow your computer? (set! pong (call/cc pong)) You'll get it back right away. I'm busy. Thank you, that was it. OK. (print "Thank you, that was it. ") (set! pong (call/cc pong)) ping 3 : Can I borrow your computer? (if (< n 5) (loop (+ n 1))))) (define (pong ping) Now what? Your'e welcome. You'll get it back right away. Now what? Thank you, that was it. I'm busy. ping 4 : Can I borrow your computer? (let loop () I'm busy OK. You'll get it back right away. Your'e welcome. (lambda (reply) Thank you, that was it. Now what? (println reply) ping 5: Can I borrow your computer? (set! ping (call/cc ping))) You'll get it back right away. OK. '("I'm busy." "OK." "Your'e welcome." "Now what?")) Thank you, that was it. Your'e welcome. (for‐each I'm busy. (loop))) Legg merke til hvordan pong blir liggende på etterskudd i dialogen. Mens ping har 3 replikker, så har pong 4, så når ping har fullført sine 5 runder har pong bare fullført 3 og 4/5 runder. Dette er mulig, fordi ping og pong utveksler kontinuasjoner og ellers forblir i hver sin verden. 15
 Når ping kaller call/cc med pong som argument, vil call/c kalle pong med den aktuelle kontinuasjonen i ping som argument.  Argumentet til pong er en pakke som inneholder ‐ status i ping, ved kallet på call/cc, der det avgjørende er verdien til n, og ‐ punktet etter kallet på call/cc, som er der utførelsen av ping evt. skal gjenopptas.  Siden pong bare kalles fra ping, via call/cc, er argumentet ping til pong alltid en slik pakke, så når pong kaller ping via call/cc, er vi tilbake til det gitte returpunktet i ping, og fortsetter derfra, som om ingenting hadde skjedd, sett fra innsiden av ping.  Setningene (set! pong (call/cc pong)) sørger for at det alltid er den siste kontinuasjonen vi fanget i pong, som sendes tilbake i neste omgang.  Tilsvarende gjelder for pong , når den kaller call/cc med ping som argument. Avgjørende for status her, er hvor langt for‐each har kommet gjennom replikklisten i pong.  En kontinuasjon er fullstendig lukket, og dermed er ping og pong helt trygge for at den ene ikke skal ta kontroll over den andre og vice versa. 16
FP vs OOP, del 2 I forhold til objektorientert programmering gjør funksjonell programmering ting mindre enklere klarere tryggere lettere å teste et cetera, et cetera. 17
Quick‐Sort i Haskell og Scheme Haskell qsort [] = [] qsort(x:xs) = qsort [y | y<‐xs, y<=x] ++ [x] ++ qsort [y | y<‐xs, y>x]. Scheme (define (qsort seq) (if (null? seq) '() (append (qsort (filter (lambda (x) (<= x (car seq))) (cdr seq))) (list (car seq)) (qsort (filter (lambda (x) (> x (car seq))) (cdr seq)))))) Disse gjør nøyaktig det samme, men Scheme‐varianten er vel lettest å lese. Det finnes mer effektive løsninger, men neppe noen som er enklere og mer elegante enn disse. I motsetning til standardvarianten, som bl.a. går ut på å swappe verdier innenfor samme vektor, er de ovenstående ikke‐mutative—basert på generering av nye lister i hver rekursjon. 18
Programmeringsparadigmer Imperativ
Eksplorativ dynamisk statisk tilordner verdier undersøker verdier og gir dermed opphav til vha. predikater, funksjoner og regler, tilstandsendring uten å endrer verden En funksjons returverdi kan variere fra kall En funksjon gir alltid til kall, selv om argumentene er de samme. samme returverdi med samme argumenter, Sekvensen er avgjørende. Sekvensen av funksjonskall er uten betydning. Resultatet er programmets tilstand Resultatet er programmets returverdi. ved terminering. ———————————————————
——————————————————— OOP er imperativ FP er eksplorativ 19
(no side‐effects) Interpretering vs. kompilering + eksekvering En IDE (Integrated Development Envirnoment) omfatter minimum en kildekodeeditor og et run‐time‐system. En imperativ IDE Den funksjonelle IDE:
‐ integererer kompilering, lenking og bygging ‐ REPL — the Read‐Eval‐Print‐Loop ‐ legger inn ad hoc entry‐points for ‐ En hvilken som helst prosedyre utførelse av deler av programmet kan sendes direkte til interpreteren, ‐ lar oss inspisere programmets tilstander ‐ hvorpå kallet utføres og på angitt break‐points ‐ mm ‐ resultatet skrives ut ‐ Testing og debugging kan i stor grad utføres i REPL (men de mest utviklede omgivelsene har også dedikerte debuggingsfaciliteter), ‐ mm 20
Utbygging av språket vha. prosedyrer og makroer. Verktøykasser. Funksjonell programmering vil i mange tilfeller innebære bruk av standardprosedyrer sammen med egne implementeringer av mer eller mindre standard algoritmer. Dette kan gjøre rammen for programmeringen mer oversiktlig, behagelig og stimulerende enn det bruken av store samlinger av store klassebibliotek gjør (uten at det dermed er sagt at prosedyrebibliotek er unyttige). 21
(sier mange — og jeg med dem), Effektivitet I gamle implementasjoner på gamle maskiner var funksjonelle språk veldig trege. Implementasjonene har gjennomgått ett eller flere kvantesprang, og det samme gjelder maskinene. (Uten at vi hermed snakker om quantum computers.) Just In Time Compilation Interprets and caches interpreted code. (JIT brukes også i andre runtimesystemer.) Alt i alt er effektivitet ikke lenger noe tema når det gjelder forholdet mellom imperativ og funksjonell programmering. Faktisk kan FP i noen tilfeller være raskere enn IP, men da dreier det seg mest om ‐ forenkling av kode og ‐ minimalisering av eksekveringsoverhead. 22
Beregninger, funksjoner og returverdier  Alle beregninger er funksjonelle, og På et abstrakt nivå—over spesifikke språk og omgivelser  alt som utføres innenfor samme tilstand, er beregninger.  Resultatet av en beregning kan bl.a. være et tall, en tilstandsspesifikasjon, en html‐side, eller en liste (fil) med millioner av poster. Betrakter vi eksterne lagringsmedier som nettop eksterne,  er de programmer som bare skriver, rent funksjonelle, og  de programmer der tilstandsendringer bare beror på lesing, alt vesentlig funksjonelle. 23
Beregninger i et grafisk brukergrensesnitt Det som står igjen er programmer som opererer på objekter som er karakteristert ved sine interne tilstander. Dette gjelder ikke minst interaktive programmer — spesielt GUI. Men også her utføres beregninger, og, hvis programmeringsomgivelsene åpner for det, er det ingen grunn til ikke å utføre alle beregninger funksjonelt. Ellers synes OOP å være et opplagt førstevalg i GUI‐programmering, men det finnes funksjonelle GUI‐implementasjoner, og det jobbes med å utvikle funksjonelle omgivelser for GUI‐programmering (uten at noen påstår at man kommer helt utenom objekter). 24
Hva hender i et grafisk brukergrensesnitt GUI‐programmering dreier seg bl.a. om håndetering av vinduer, rullesjakter, museklikk, etc. og behandling og videresending av event‐meldinger. Her hender det at rammene — the framework pålegger programmet kompliserte objektkonfigurasjoner med mye redundans, og forvirrende dispatch‐linjer. Dette gir uoversiktlige kall‐kjeder og dårlig message tracking (mellom objekter). 25
Kallkjeder i et funksjonelt program I et funksjonelt program finner man kallkjeden uten videre og det er der den vesentlige informasjonen ligger: Hva skjedde her, og hvorfor, og hva ble resultatet? En kallkjede i et funksjonelt program bygges bl.a. ved komposisjon (accumulate + (map square (filter prime? (enumerate 1 50)))) ==> 10381 eller pattern matching 26
(se neste side) (define (leap‐year? y) (cond ((divisible y 400) #t) ((divisible y 100) #f) ((divisible y 4) #t) (else #f))) (define (month‐length y m) (case m ((4 6 9 11) 30) ((1 3 5 7 8 10 12) 31) (else (if (leap‐year? y) 29 28)))) (define (date+days‐>date y m d n) ; find the date n days from a given date (define (iterate y m n) (cond ((<= n (month‐length y m)) (list y m n)) ; done ((= m 12) (iterate (+ y 1) 1 (‐ n (month‐length y m)))) ; new year (else (iterate y (+ m 1) (‐ n (month‐length y m)))))) ; new month (if (< m 12) (iterate y (+ m 1) (‐ n (‐ (month‐length y m) d))) (iterate (+ y 1) m (‐ n (‐ 31 d))))) 27
En del av et event‐drevet program kunne se slik ut (define (handle‐event e) (cond ((mouse‐event? e) (handle‐mouse‐event e)) ((keybord‐event? e) (handle‐keyboard‐event e)) ... (else <ignore or raise exception>))) (define (handle‐keyboard‐event e) (cond ((BAND e ctl‐flag) (handle‐ctl‐key e) ((BAND e (+ shift‐flag alt‐1‐flag alt‐2‐flag)) (handle‐modified‐key e)) (else (handle‐straight‐key e))) Til syvende og sist vil dette kunne ende med en tilstandsendring, men frem dit følger event‐behandlingen et funksjonelt mønster. Og, om man nøyer seg med å rapporterer hva som skal gjøres, og overlater utførelsen til kallende prosedyre, har man en rent funksjonell subrutine. Jevnfør Hickleys beskrivelse av concurrent programming under. 28
Concurrency To prosesser kan utføres samtidig, concurrently, uten på noen måte å affektere hverandre, men Et mye brukt eksempel på concurrency er hvis to prosesser den delte bankkonto. Hva skjer når to personer opererer samtidig samtidig prøver å ta ut penger kreves på samme foranderlige objekt, fra samme konto? Men concurrent programming er også relevant andre steder tilstandsprogrammering. som f.eks. i robotikk og styringssystemer. Allikevel regnes concurrency for å være et område der funksjonell programmering egner seg godt. 29
1. If you're in a heavily concurrent environment, then pure functional programming is useful. The lack of mutable state makes concurrency almost trivial. 2. In a multiparadigm language, you may want to model some things functionally if the existence of mutable state is just an implementation detail, and thus FP is a good model for the problem domain. 30
David Simcha, stackoverflow, 16.12.2010. Clojure er et Lisp‐basert, funksjonelt språk utviklet av Rich Hickey, med Take a snapshot of the current state, pass it to a function, and apply the return value to set the new state, if the state hasn't already been changed by someone else. Poenget er at et snapshot per se er en verdi og som sådan immuterbar. mekanismer for implementing inherently concurrent, state sharing, operations Fritt etter Rich Hickey 2008‐2011 http://clojure.org/state 31
Controlling State An instruction can only be expected to work properly if the world stands still when it is executed. State‐oriented programming must be able to control state completely. Complete control of state is not possible from any single point of view in a concurrent environment. Threads fight over state control. A function execution is thread safe. It is local and selfcontained and not affected by changes of state. Nokså fritt etter Rich Hickey 2008‐2011 http://clojure.org/state 32
Erlang er et programmeringsspråk og runtime system, utviklet ved Ericsson, med et funksjonelt subset med mekanismer for concurrent programmering. Erlang bruker message passing mellom aktørene i systemet. i stedet for shared state. 33
Sharing state vs sharing a message handler Imperativ tilnærming med lock  prosessene deler en mengde ressurser og  vil prosessen P gjøre noe med en ressurs må den skaffe seg en hengelås  som utelukker andre prosesser fra å gjøre noe med den aktuelle ressursen (f.eks. skrive til den).  inntil P er ferdig, låser opp ressursen og gir fra seg hengelåsen Går det så går det, det får handleren sortere ut, og uansett går ikke verden under  Vi har noe vi ønsker å få gjort og beregner omkostninger og resultater funksjonelt.  Det vi ønsker kan få konsekvenser for felleskapet, så vi melder vårt ønske til en message handler, sammen med våre beregninger.  Behandleren, hvis vi får dens oppmerksomhet, gjør det vi ber om, i en atomær prosess, som ekskluderer alle andre fra det saken gjelder.  Om behandleren var opptatt, og det som skjedde mens vi ventet, endret foutsetningene for våre beregninger, oppdaterer vi beregningene og prøver igjen—eller går videre til noe annet. 34
WEB utvikling In WEB development OOP, design patterns, layered architectur etc. seem to be redundant, an oversolution to really small problems. 1. An application receives the user input, validates it, transforms it into a domain‐specific format and passes it to an SQL code which puts it into the database. 2. The code reads some other data from the database, reformats it to be user‐friendly then serves back a piece of HTML. That's it. No fancy objects living their lives, sending and receiving messages and intelligently interacting among themselves to achieve a higher behavior. It's data‐flow one‐on‐one. The implementation is essentially functional, regardless of the style it was conceived to bear. Fritt etter http://developerart.com/publications/22/web‐programming‐is‐functional‐programming. 35
For hobbyister: Organisering av passive web‐sider Har man en samling bilder, med én nettside per bilde med forrige‐ og neste‐lenker, lærer man fort at det koster å sette inn og ta ut bilder, for ikke å snakke å omorganisere samlingen. Man kunne skrive et program som leste inn alle sidene til en muterbar liste, og gjøre endringene interaktivt der, eller lage en ekstern liste med titler, datoer etc. og skrive et program som mappet fra listen til mengden av filer. (Ettersom basen vokser vil man uansett trenge en billedliste som man redigerer utenfor programmet—i Emacs, TextPad, e.l.). Den siste løsningen har tre avgjørende fortrinn ‐ den er brukervennlig (mens den første løsningen nærmest er ubrukelig), ‐ krever knapt nok mer kode den html‐side den produserer, og ‐ er meget rask. 36
Jeg har en samling med nærmere 3000 bilder fra balkongen, organisert i 40 tematiske lister (hver man sin hobby). http://folk.uio.no/asbr/balcony/vista.html Man kan velge mellom tre billedstørrelser, og siden html‐sidene ikke er aktive, gir dette nærmere 9000 sider. En full generering av basen tar 2‐3 minutter. 37
Parsing Parsing går (i all enkelhet) ut på å ‐ ta imot en streng, ‐ transformere/flytte strengen til et tre i hht. syntaktiske og grammatiske regler, og ‐ returnere treet. Her boyfriend always ate
empanadas on fry days, she said
Parsing brukes både for naturlige og formelle språk—det siste typisk i compilatorer. Functional languages excel at manipulating symbolic data in tree form. […] Compilation and translation more generally are ʺkiller appsʺ for functional languages. Philip Wadler, The Expression Problem, 12 .11. 1998 38
Databehandling Databehandling dreier seg om statiske forholdet mellom verdier, og er som sådan funksjonell. Databehandling kan sees på som signalprosessering. ekstrahér  transformér  filtrér  sortér  akkumulér der hvert ledd er et funksjonskall (akkumulér kombinator (sortér ordning (filtrér predikat (transormér transformator (ekstrahér selektor signalsekvens))))) Her er kombinator, ordning, predikat, transformator og selektor alle prosedyrer. 39
Eksempel 1: Sekvensiell databehandling: Prosessering av nedbørsdata. Vi har en liste A, med kronologisk ordnede tripler (år, måned, nedbør) og ønsker en liste D, med par, (måned, gjennomsnitt‐nedbør/måneden), basert på A. Vi gjør som følger, når a = antall hele år i A: (a) omformer, chops, A til B, en liste med a lister med 12 tripler i hver (b) transponer B til en liste C, med 12 lister med a tripler hver, dvs. der hver liste dekker én måned gjennom a år. (c) mapper fra C til D der hver rad i C er (d) foldet sammen til gjennomsnittet av radens nedbørsmengder prefikset med det aktuelle månedsnavnet. (map (lambda (row) (list (cadar row) (average (map caddr row)))) (transpose (chop‐list A 12))) 40
A ((2001 jan 18) (2001 feb
B 4) (2001 mar 54) (2001 apr 18) (2001 may
((2001 jan 18) (2001 feb
0) (2001 jun 18) (2001 jul
4) (2001 aug
6) (2001 sep 32) (2001 oct 12) (2001 nov 80) (2001 dec 50) (2002 jan 27) (2002 feb 12) (2002 mar 15) (2002 apr 14) (2002 may
2) (2002 sep 12) ... )
 chop  4) (2001 mar 54) (2001 apr 18) (2001 may
0) (2001 jun 18) (2001 jul
4) (2001 aug
6) (2001 sep 32) (2001 oct 12) (2001 nov 80) (2001 dec 50)
8) (2002 jun 17) (2002 jul
3) (2002 aug
2) (2002 sep 12) (2002 oct 56) (2002 nov 40) (2002 dec 40)
0) (2003 apr 32) (2003 may 10) (2003 jun
6) (2003 jul
0) (2003 aug 19) (2003 sep 68) (2003 oct 76) (2003 nov 85) (2003 dec 30))
 transpose C  (((2001 jan 57) (2002 jan 45) (2003 jan
D 6))
((jan 26.0)
((2001 feb 52) (2002 feb 56) (2003 feb 76))
(feb 29.3)
((2001 mar 21) (2002 mar
(mar 33.0)
0) (2003 mar 18))
((2001 apr 12) (2002 apr 36) (2003 apr 20))
(apr 15.3)
((2001 jul 15) (2002 jul
4) (2003 jul
5))
 map‐fold 
3)) (jul
6.7)
((2001 aug 5)
8) (2003 aug
8))
(aug
8.0)
((2001 sep 28) (2002 sep 36) (2003 sep
0))
(sep 61.3)
((2001 may
7) (2002 may 18) (2003 may 18))
((2001 jun 17) (2002 jun 15) (2003 jun
(2002 aug
((2001 oct 52) (2002 oct
7.0)
(jun 14.0)
(oct 21.3)
(nov 56.7)
((2001 dec 40) (2002 dec 60) (2003 dec 35)))
(dec 78.3))
0) (2003 oct 72))
(may
((2001 nov 90) (2002 nov 20) (2003 nov 45))
3) (2002 aug
(2002 jan 27) (2002 feb 12) (2002 mar 15) (2002 apr 14) (2002 may
(2003 jan 18) (2003 feb 24) (2003 mar
8) (2002 jun 17) (2002 jul
Foldingen består i at vi samler tallene i hver rad til ett gjennomsnitt 41
(map (lambda (row) (list (cadar row) (average (map caddr m‐row)))) (transpose (chop‐list A 12))) average, transpose og chop‐list har vi definert selv, samt slice‐list som brukes av chop‐list. Vi kan se på average og transpose. (define (average numbers) (exact‐>inexact (/ (apply + numbers) (length numbers)))) average tar en liste med tall, nums, anvender prosedyren + på listen og deler resultatet på lengden av listen (define (transpose m) (if (null? (car m)) '() (cons (map car m) (transpose (map cdr m))))) transpose tar en matirse, m, dvs. en liste med like lange lister, der første kolonne er alle første‐elementene i listene, osv. transpose flytter seg rekursivt kolonne for kolonne gjennom m, og for hver runde gir (map car m) listen med alle første elementene og (map cdr m) listen med alle restlistene. 42
Det at vi kan gjøre forholdsvis kompliserte ting med enkle midler, gjør det også lett å lage testdata: (define (enumerate a b) (if (> a b) '() (cons a (enumerate (+ a 1) b)))) (define months '(jan feb mar apr may jun jul aug sep oct nov dec)) (define weights '( 3 4 3 2 1 1 1 1 4 4 5 5)) ; nedbørsannsynligheter (define (generate‐test‐months n‐monts) (map (lambda (month) (list (+ 2001 (quotient month 12)) ; årstall (list‐ref months (remainder month 12)) ; månedsnavn (* (random 20) (list‐ref weights (remainder month 12))))) ; nedbørsmengde (enumerate 0 (‐ n‐monts 1)))) (generate‐test‐months 40) ==> (3 år og 4 måneder) ((2001 jan 18) (2001 feb 4) (2001 mar 54) (2001 apr 18) (2001 may 0) (2001 jun 18) (2001 jul 4) (2001 aug 6) (2001 sep 32) (2001 oct 12) (2001 nov 80) (2001 dec 50) (2002 jan 27) (2002 feb 12) (2002 mar 15) (2002 apr 14) (2002 may 8) (2002 jun 17) (2002 jul 3) (2002 aug 2) (2002 sep 12) (2002 oct 56) (2002 nov 40) (2002 dec 40) (2003 jan 18) (2003 feb 24) (2003 mar 0) (2003 apr 32) (2003 may 10) (2003 jun 6) (2003 jul 0) (2003 aug 19) (2003 sep 68) (2003 oct 76) (2003 nov 85) (2003 dec 30) (2004 jan 57) (2004 feb 60) (2004 mar 54) (2004 apr 16)) 43
(chop‐list (generate‐test‐months 40) 12) ==> ((2001 jan 18) (2001 feb 4) (2001 mar 54) (2001 apr 18) (2001 may 0) (2001 jun 18) (2001 jul 4) (2001 aug 6) ... (2001 dec 50) (2002 jan 27) (2002 feb 12) (2002 mar 15) (2002 apr 14) (2002 may 8) (2002 jun 17) (2002 jul 3) (2002 aug 2) ... (2002 dec 40) (2003 jan 18) (2003 feb 24) (2003 mar 0) (2003 apr 32) (2003 may 10) (2003 jun 6) (2003 jul 0) (2003 aug 19) ... (2003 dec 30)) (transpose (chop‐list (generate‐test‐months 40) 12)) ==> (((2001 jan 57) (2002 jan 45) (2003 jan 6)) ((2001 feb 52) (2002 feb 56) (2003 feb 76)) ((2001 mar 21) (2002 mar 0) (2003 mar 18)) ((2001 apr 12) (2002 apr 36) (2003 apr 20)) ((2001 may 7) (2002 may 18) (2003 may 18)) ((2001 jun 17) (2002 jun 15) (2003 jun 5)) ((2001 jul 15) (2002 jul 4) (2003 jul 3)) ((2001 aug 5) (2002 aug 8) (2003 aug 8)) ((2001 sep 28) (2002 sep 36) (2003 sep 0)) ((2001 oct 52) (2002 oct 0) (2003 oct 72)) ((2001 nov 90) (2002 nov 20) (2003 nov 45)) ((2001 dec 40) (2002 dec 60) (2003 dec 35))) (map (lambda (row) (list (cadar row) (average (map caddr m‐row)))) (transpose (chop‐list (generate‐test‐months 40) 12))) ==> ((jan 26.0) (feb 29.3) (mar 33.0) (apr 15.3) (may 7.0) (jun 14.0) (jul 6.7) (aug 8.0) (sep 61.3) (oct 21.3) (nov 56.7) (dec 78.3)) 44
Eksempel 2: Objekter med lokal tilstand: En randomgenerator (define (make‐MWC‐random‐generator x) Alle randomgeneratorer gir sekvenser som (let ((m (‐ 2^32 1)) til syvende og sist gjentar seg—periodiske (a 2147483085) sekvenser. MWC utmerker seg ved å gi (c (+ x 1))) (lambda () gigantiske periodelengder hinsides enhver (let ((prev‐x x)) forståelse—opp mot 10800000. Til sammen‐
(set! x (modulo (+ (* a x) c) m)) ligning er det ca 1080 atomer i universet . (set! c (quotient (+ (* a prev‐x) c) m)) x)))) MWC står for Multiply With Carry. Det ville være lite lurt å operere med en funksjonell randomgenerator, siden vi da hadde vært nødt til å sende m, a og c rundt som argumenter. 45
Eksempel 3: Objekter med lokal tilstand: En kalkulator (define (make‐calculator) (let ((memory 0)) (lambda obj ; a list which is either empty or contains a number or an operator (cond ((null? obj) memory) ; empty ((number? (car obj)) (set! memory (car obj)) memory) ; a number (else (lambda (x) (set! memory ((car obj) memory x)) memory)))))) ; an operator make‐calculator returnerer et prosedyreobjekt
‐ Er obj tom, returnes memory. (en closure) med tilstandsvariabelen memory. ‐ Inneholder obj et tall, settes memory til dette. ‐ Inneholder obj en operator, returneres en Returprosedyren tar intet eller ett argument som, prosedyre som ‐ tar et tallargument, ‐ anvender operatoren på memory og tallet, ‐ og setter memory til resultatet. sett fra prosedyrekroppen, er en liste, obj. En enkel test: (define calculator (make‐calculator)) (calculator 5) ==> 5 ((calculator ‐) 2) ==> 3 ((calculator *) 7) ==> 21 46
En tilstandsbasert —ikke‐funksjonell—prosedyre som anvender kalkulatoren på input fra bruker (define (use‐calculator calculator) ... (define (consume) (if (char=? (peek‐char) #\newline) ; when the user is done and presses enter (calculator) ; we return the calculator's memory content (begin ((calculator (read‐arg good‐operator)) (read‐arg good‐number)) (consume)))) (calculator (read‐arg good‐number)) ; read the first number (consume)) ; consume subseq items two by two, operator followed by number Først leses første tall og deretter kalles den lokale rekursive prosedyren consume. use‐calculator tar en kalkulator, leser item for item fra bruker og sender disse til kalkulatoren etter tur. consume leser de etterfølgende items to og to, read‐arg sjekker argumenttypen, først operatoren og deretter tallet. så vi kan gå ut fra at vi får inn et tall og en operator annenhver gang, og at første og siste item er et tall.
Operatoren sendes til kalkulatoren som returnerer en prosedyre som får tallet som argument. 47
Noen funksjonelle språk Pure Charity Clean Curry Haskell Hope Miranda Allow assignments APL Candle Curl Erlang F# FPr CAL Hop J Joy Lisp Clojure Common Lisp Dylan eLisp Little b Logo Scheme Racket Tea Mathematica ML Standard ML Alice Ocaml Nemerle
Opal OPS5 Poplog Q (equational) Q (from Kx Systems) R REFAL Scala Functional and object oriented Ada BETA C# CLOS Cobra 48
Curl D Dylan ECMAScript F# Fantom FPr Leda Lua Objective Caml Oz Perl Python Racket Ruby Scala Tcl Tea Windows PowerShell KODEEKSEMPLER
Kalkulator
;——————————————————————————————————————————————————————————————————————————————— (define (make‐calculator) (let ((memory 0)) (lambda obj ; a list, either empty or containing a number or an operator (cond ((null? obj) memory) ((number? (car obj)) (set! memory (car obj)) memory) (else (lambda (x) (set! memory ((car obj) memory x)) memory)))))) ;——————————————————————————————————————————————————————————————————————————————— (define (use‐calculator calculator) (define (good‐symbol x) (and (symbol? x) x)) ; returns x, if it is a symbol, or #f (define (good‐number x) (and (number? x) x)) ; returns x, if it is a number, or #f (define (good‐operator x) ; returns x, if it is an operator, or #f (and (namespace‐defined? x) (procedure? (eval x)) (eval x))) ;——————————————————————————————————————————————————————————————————————————————— ; Takes a type semi‐predicate and an optional message ; If the predicate is not satisfied, the procedure ; calls itself with an appropriate message (define (read‐arg good‐type . message‐to‐user) (if (not (null? message‐to‐user)) (begin (display (car message‐to‐user)) (newline))) (or (good‐type (read)) (read‐arg good‐type "Illegal input"))) ;——————————————————————————————————————————————————————————————————————————————— ; Assumes that either there is no more input or that ; an operator and a number is in the input buffer. (define (consume) (if (char=? (peek‐char) #\newline) (calculator) (begin ((calculator (read‐arg good‐operator)) (read‐arg good‐number)) (consume)))) ;——————————————————————————————————————————————————————————————————————————————— ; Read first number argument (calculator (read‐arg good‐number)) ; Read subsequent operator and number arguments (consume)) 49
Nedbørsdata
;——————————————————————————————————————————————————————————————————————————————— ; Standard stuff (define (enumerate a b) (if (> a b) '() (cons a (enumerate (+ a 1) b)))) (define (average nums) (exact‐>inexact (/ (apply + nums) (length nums)))) ;——————————————————————————————————————————————————————————————————————————————— ; Generate test data (define months '(jan feb mar apr may jun jul aug sep oct nov dec)) (define weights '( 3 4 3 2 1 1 1 1 4 4 5 5)) (define (generate‐test‐months n‐monts) (map (lambda (month) (list (+ 2001 (quotient month 12)) (list‐ref months (remainder month 12)) (* (random 20) (list‐ref weights (remainder month 12))))) (enumerate 0 (‐ n‐monts 1)))) ;——————————————————————————————————————————————————————————————————————————————— ; Return a pair of two lists: ; (a) containing the first k elements in lst and ; (b) the rest of lst. (define (slice‐list lst k) (define (iter front tail n) (if (zero? n) (cons (reverse front) tail) (iter (cons (car tail) front) (cdr tail) (‐ n 1)))) (iter '() lst k)) ;——————————————————————————————————————————————————————————————————————————————— ; Return the list of evenly chopped list segments of lst. ; If size does not divide (length lst) the residue is ignored (define (chop‐list lst size) (define (iterate lst chopped) (if (< (length lst) size) (reverse chopped) (let ((parts (slice‐list lst size))) (iterate (cdr parts) (cons (car parts) chopped))))) (iterate lst '())) ;——————————————————————————————————————————————————————————————————————————————— ; Let M be a matrix with r rows and c columns. ; Return a matrix N with c rows and r columns, ; such that M[i, j] = N[j, i]. (define (transpose m) (if (null? (car m)) '() (cons (map car m) (transpose (map cdr m))))) ;——————————————————————————————————————————————————————————————————————————————— (map (lambda (m‐row) (list (cadar m‐row) (average (map caddr m‐row)))) (transpose (chop‐list (generate‐test‐months 40) 12))) 50
Ref: Abelson & Sussman: Structure and Interpretation of Computer Programs, 1996. MIT Press. http://mitpress.mit.edu/sicp/.
Simon Thompson: The Craft of Functional Programming, second edition, 1999. Addison Wesley.
51
Opprettet 2.3.2012.
Endret 16.3.2012.
52
Download