Prosedyreobjekter og prosedyrer av høyere orden

Like dokumenter
Prosedyreobjekter og prosedyrer av høyere orden

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Dataabstraksjon og Trerekursjon

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

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

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))

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

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

Eksamen i SLI230, vår 2003.

INF2810: Funksjonell Programmering. En metasirkulær evaluator

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

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering. En metasirkulær evaluator

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.

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

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

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

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

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

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

Høyere-ordens prosedyrer

INF2810: Funksjonell Programmering. Eksamensforberedelser

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

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. En metasirkulær evaluator, del 2

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

INF2810: Funksjonell Programmering. Tilstand og verditilordning

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

UNIVERSITETET I OSLO

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

INF2810: Funksjonell Programmering. Muterbare data

INF2810: Funksjonell Programmering. Mengder og lokal tilstand

INF2810: Funksjonell Programmering. Mer om strømmer

INF2810: Funksjonell Programmering. Mer om strømmer

INF2810: Funksjonell Programmering. Kommentarer til prøveeksamen

LISP PVV-kurs 25. oktober 2012

INF2810: Funksjonell Programmering. Tilstand og verditilordning

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

INF2810: Funksjonell Programmering. Tilstand og verditilordning

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

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

INF2810: Funksjonell Programmering. Strømmer og utsatt evaluering

INF2810: Funksjonell Programmering. Tilstand og verditilordning

INF2810: Funksjonell Programmering. Strømmer og utsatt evaluering

Memoisering, utsatt evaluering og strømmer

Memoisering, utsatt evaluering og strømmer

Innlevering 2a i INF2810, vår 2017

Oppgave 1 Minimum edit distance

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

Innlevering 2b i INF2810, vår 2017

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

Semantisk Analyse del I

Kapittel 1 En oversikt over C-språket

Plan: Parameter-overføring Alias Typer (Ghezzi&Jazayeri kap.3 frem til 3.3.1) IN 211 Programmeringsspråk

Gauss-Jordan eliminasjon; redusert echelonform. Forelesning, TMA4110 Fredag 18/9. Reduserte echelonmatriser. Reduserte echelonmatriser (forts.

Appendiks A Kontinuasjoner

Typer. 1 Type: boolean. 2 Verdimengde: {true, false} 3 Operatorer: NOT, AND, OR... 1/19. Forelesning Forelesning

INF3110 Programmeringsspråk. Dagens tema. Typer (Kapittel 3 frem til ) Innføring i ML (Kapittel & ML-kompendiet.) 1/19

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

INF2810: Funksjonell programmering: Introduksjon

INF2810: Funksjonell Programmering. 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. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Strømmer

INF2810: Funksjonell Programmering. Utsatt evaluering og strømmer

INF2810: Funksjonell Programmering. Huffman-koding

Kort om meg. INF1000 Uke 2. Oversikt. Repetisjon - Introduksjon

Løse reelle problemer

INF2810: Funksjonell Programmering. Muterbare data

Oversikt. INF1000 Uke 2. Repetisjon - Program. Repetisjon - Introduksjon

Beskrivelse av programmeringsspråket Compila15 INF Kompilatorteknikk Våren 2015

MAT1030 Diskret Matematikk

IN 147 Program og maskinvare

2 Om statiske variable/konstanter og statiske metoder.

INF2810: Funksjonell Programmering. Huffman-koding

Symbolske data SICP 2.3

Litt om Javas håndtering av tall MAT-INF 1100 høsten 2004

Lineære ligningssystemer og gausseliminasjon

MAT1030 Forelesning 2

Likninger - en introduksjon på 8. trinn Hva er en likning og hva betyr å løse den?

4.1 Vektorrom og underrom

Sekventkalkyle for utsagnslogikk

Transkript:

Prosedyreobjekter og prosedyrer av høyere orden Her er noen prosedyrer for summering av rekker av tall basistilfellet, initialverdi almenntilfellet, summeringen (define (sum-integers a b) (if (> a b) 0 (+ a (sum-integers (+ a 1) b)))) (define (sum-squares a b) (if (> a b) 0 (define (sum-cubes a b) (if (> a b) 0 (define (sum-roots a b) (if (> a b) 0 (define (pi-sum a b) (if (> a b) 0 (+ (square a) (sum-squares (+ a 1) b)))) (+ (cube a) (sum-cubes (+ a 1) b)))) (+ (sqrt a) (sum-roots (+ a 1) b)))) (+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b)))) 1 1 1 Den siste er basert på rekken /8 = + + + 1 3 5 7 9 11 121

Tilsammen fremviser disse et mønster med variasjoner, tilsvarende en prosedyre som tar varierende argumenter, med den forskjell at variasjonene her ikke angår data, men selve fremgangsmåten. Mønsteret består i at vi summerer en sekvens av funksjonsverdier, mens variasjonene består i funksjonen og endringen av argumentet til denne. summering funksjon argumentendring sum-heltall f(x) = x next(x) = x + 1 sum-kvadrater f(x) = x 2 next(x) = x + 1 sum-kuber f(x) = x 3 next(x) = x + 1 sum-røtter f(x) = x next(x) = x + 1 pi-sum f(x) = 1/(x 2 + 2x) next(x) = x + 4 I Scheme kan vi bruke prosedyrer som argumenter, på linje med bl.a. tall og vi sier at er prosedyrer er førsteklasses objekter. 122

Vi kan i lys av dette definere den generelle summeprosedyren slik (define (sum f ; funksjonen a ; argumentet til f next ; iteratoren, fremdriveren b) ; basisverdien, målverdien (if (> a b) 0 (+ (f a) (sum f (next a) next b)))) Vi har nå gjort f til en høyereordensprosedyre, dvs. en prosedyre som tar ett eller flere prosedyreargumenter eller returnerer prosedyrer eller begge deler. For de fire første summene trenger vi en prosedyre for inkrementering av argumentverdien samt identitetsfunskjonen og prosedyrer for kvadrering, kubering og kvadratrotsutrekking (define (inc i) (+ i 1)) (define (identity x) x) (define (square x) (* x x)) (define (cube x) (* x x x)) kvadratrotsprosedyren sqrt er en Scheme-primitiv, 123

Med dette kan vi skrive om de fire første spesifikke summeringsprosedyrene slik. (define (sum-integers a b) (sum identity a inc b)) (define (sum-squares a b) (sum square a inc b)) (define (sum-cubes a b) (sum cube a inc b)) (define (sum-roots a b) (sum sqrt a inc b)) Merk at selv om ikke identity, square, cube og inc er primitiver eller biblioteksrutiner, er de generelle nok til å kunne ha forsvart en plass blant disse på linje med sqrt. Det samme kan vi imidlertid ikke si om de funksjonene vi bruker i pi-sum, og vi definerer derfor disse ad hoc som lokale prosedyrer. (define (pi-sum a b) (define (f x) (/ 1.0 (* x (+ x 2)))) ; lokal prosedyre for beregning av funksjonsverdi (define (next x) (+ x 4)) ; lokal prosedyre for endring av argumentverdi (sum f a next b)) ; kroppen til pi-sum 124

Lambda Den tilgrunnliggende mekanismen for prosedyredefinering i Scheme er Lambda. Formen (define (<prosedyrenavn> <formelle parametre>) <prosedyrekopp>) som vi har brukt så langt, er egentlig en omskrivning av (syntaktisk sukker for) formen (define <prosedyrenavn> (lambda (<formelle parametre>) <prosedyrekopp>)) Eks: (define (square x) (* x x)) (define square (lambda (x) (* x x))) Nå behøver ikke et lambda-uttrykk som dette alltid stå inne i et definisjonsuttrykk. Det kan faktisk opptre helt på egenhånd f.eks. slik: ((lambda (x) (* x x)) 3) 9 I standard lambda-notasjon (se første forelesning): ( x. x 2 )(3), NB! dette er ikke et S-uttrykk. Et S-uttrykk består står av en parentes med en prosedyre på første plass. 125

Legg merke til parentesen rundt lambdauttrykket og det etterfølgende tallet. Regelen for evaluering av et sammensatt uttrykk sier at vi først skal evaluere leddene og deretter anvende det første resultatet på de øvrige (om noen). Gitt den siste definisjon av kvadrat, er ((lambda (x) (* x x)) 3) (square 3) Det ser vi også om vi her setter inn for definiens (lambda (x) (* x x)) definiendum square i et kall på square. (square 3) ((lambda (x) (* x x)) 3) square evaluerer per definisjon til (lambda (x) (* x x)). 126

Lambda er en spesialform (med samme enkle parentessyntaks som alle andre Scheme-former) som returnerer en prosedyre. Og når lamda-uttrykket er evaluert, kan den returnerte prosedyren brukes som en hvilken som helst annen prosedyre, uansett hvor lambda-uttrykket måtte opptre. For en prosedyre som pi-sum, der vi nettopp har bruk for ad hoc engangsprosedyrer, betyr dette at vi kan legge de aktuelle prosedyrene direkte inn på argumentplassene i kallet på sum. Opprinnelig versjon Ny versjon (define (pi-sum a b) (define (pi-sum a b) (define (f x) (/ 1.0 (* x (+ x 2)))) (sum (lambda (x) (/ 1.0 (* x (+ x 2)))) (define (next x) (+ x 4)) a (sum f a next b)) (lambda (x) (+ x 4)) b)) 127

Let, let* og letrec I prinsippet kan lambda-konstruksjonen sies å ligge til grunn for alt. La oss først se på en enkel variabeldefinisjon med en påfølgende bruk av den definerte variabelen. (define v 5) (* v 2) 10 Dette kan, i tråd med lambda-notasjon, forstås som en avledning av ((lambda (v) med den forskjell at skopet til v ikke er globalt, (* v 2)) 5) men begrenset til kroppen til lambda-uttrykket. 10 En annen avledning får vi med formen let (let ((v 5)) Den enste forskjellen mellom de siste er leseligheten. (* v 2)) I let-uttrykket er det lettere å se hva som bindes til hva. 10 128

De tre formene representert ved uttrykkene (define v 5) (* v 2) 10 ((lambda (v) (* v 2)) 5) 10 (let ((v 5)) (* v 2)) 10 er altså ekvivalente, men let-formen har et par fortrinn mht. leselighet. - I forhold til define-konstruksjonen er fordelen med let at vi tydelig angir, og avgrenser, det området i programmet der bindingen mellom variabelen og dens verdi gjelder. - I forhold til lambda-konstruksjonen er fordelene med let at vi angir den verdien variabelen skal bindes til foran kroppen. 129

Den generelle formen til let er (let ((v 1 e 1 ) ; variable 1 expression 1 (v 2 e 2 ) (v n e n )) <kropp>) Merk at bindingene mellom variablene og deres verdier ikke gjøres gjeldende før i kroppen. Det betyr at vi ikke kan definere én variabel i den innledende listen vha. en annen. (let ((a 1) b) (b (+ a 1))) ; ULOVLIG Her kan ikke returverdien til uttrykket bestemmes, fordi verdien til b forsøkes definert ved en ennå ikke definert a. For å løse dette kan vi bruke nøstede let-uttrykk: (let ((a 1)) (let ((b (+ a 1))) b) 130

Eller vi kan bruke forenklingen let*, som nettopp sikrer at let* er syntaktisk sukker variablene bindes i tur og orden allerede i variabellisten. for nøstet let. (let* ((a 1) b) (b (+ a 1))) Vi kan også bruke let til å definere prosedyrer (let ((p (lambda (x) (+ x 2)))) 4 (p 2)) Men for å definere rekursive prosedyrer må vi bruke letrec. (letrec ((fac (lambda (n) (if (= n 1) 1 (* n (fac (- n 1))))))) ; Siste høyreparentes svarer til ; første venstrparentes etter letrec. (fac 5)) ; Siste høyreparentes svarer til ; venstrparentesen foran letrec. 120 131

Binding av variabler Vi kaller forholdet mellom en variabel og dens verdi en binding. Definerte variabler er bundet mens udefinerte variabler er frie. (define f (lambda (y)(+ x y))) y er bundet men x er fri (define g (lambda (x)(lambda (y)(+ x y)))) både x og y er bundet Et forsøk på å bruke en fri variabel gir kjøreavbrudd. x crasher fordi x er fri (f 5) crasher fordi x er fri ((g 3) 5) går bra Prosedyreparametre er formelt bundne variabler i prosedyrens kropp. Prosedyreparametre er rent konkret bundet den forstand at idet prosedyren utføres er så er variablene bundet til argumentverdiene. 132

Eksempel: Regning på datoer For datoer innefor et århundre ønsker vi å kunne - konvertere mellom enkeltvise åttesifrede tall og tripler med år måned og dag, og - sjekke om en gitt dato er lovlig. Som enkelttall skal datoen ha formen aaaammdd. F.eks. 1997.5.17 skal skrives 19970117. * Vi lager et slikt tall fra en gitt datotrippel ved å - gange året med 10000, - måneden med 100 og - legge sammen resultatene av disse multiplikasjonene og dagen. F.eks. gir 17. mai 1997: 1997 * 10000 + 5 * 100 + 17 = 19970517. For å komme tilbake til trippelen må vi for å få - året, dele (heltallsdividere) på 1000, år = 19970517/10000 = 1997 - måneden, først dele på 100 og deretter dele måned = resten((19970517/100), 100) = 5 resultatet på 100, og ta resten etter divisjonen, - dagen, dele på 100, og ta resten etter divisjonen. dag = resten(19970517, 100) = 17 * Det er verdt å merke, selv om det ikke har betydning her, at denne formen uten vider gir rett sorteringsorden for datoer. 133

Fra ddmmaa til (dag måned år) Vi nøyer vi oss med å konvertere den ene veien fra tall til dato-trippel. Til dette benytter vi funksjonene quotient og remainder. quotient tar to tallargumenter a og b og returnerer resultatet av divisjonen a/b avrundet ned til nærmeste hele tall. (Se også truncate.) (quotient 13 5) 2 remainder tar to heltallsargumenter a og b og returnerer resten etter heltallsdivisjonen a/b. (remainder 13 5) 3 (13 / 5 = 2, og 13-2 5 = 3) Lar vi n være det åttesifrede tallet, er datotrippelen gitt ved: år: (quotient n 10000) måned: (remainder (quotient n 100) 100) dag: (remainder n 100) For å sjekke datoens lovlighet må vi sjekke dagen mot de ulike månedslengdene, og i den forbindelse må vi sjekke spesielt for februar om året er et skuddår. Her bruker vi predikatet leapyear? som er det samme som skuddår? i et tidligere eksempel. 134

Implementasjon Vi er nå klare til å skrive funksjonen eight-digits->date-triple. Funksjonen tar et tall som argument og returnerer om mulig en lovlig datotrippel eller #f. (define (eight-digits->date-triple n) (let ((year (quotient n 10000)) (month (remainder (quotient n 100) 100))) (if (or (< month 1) (> month 12)) #f (let ((day (remmainder n 100)) (maxday (cond ((or (= month 1) (= month 3) (= month 5) (= month 7) (= month 8) (= month 10) (= month 12)) 31) ((or (= month 4) (= month 6) (= month 9) (= month 11)) 30) (else (if (leapyear? year) 29 28))) (and (>= day 0) (<= day maxday) (list day month year))))) ;* (eigth-digits->date-triple 19970117) (1997 5 17) * Hvis de to første argumentene til and evaluerer til true, blir også det siste argumentet evaluert, og siden dette ikke evaluerer til false, evealuerer heller ikke and-utrrykket til false, men derimot til resultatet av evalueringen av det siste argumentet, nemlig (list day month year). 135

Hvor skal year-variabelen introduseres? year brukes ikke utenfor alternativet i if-uttrykket, og burde kanskje ha vært introdusert der. (if (or (< month 1) (> month 12)) #f (let ((year (quotient n 10000)) (day (remainder n 100)) (maxday (cond ((or (= month 1) (= month 3) (= month 5) (= month 7) (= month 8) (= month 10) (= month 12)) 31) ((or (= month 4) (= month 6) (= month 9) (= month 11)) 30) (else (if (leapyear year) 29 28))...) Dette vil imidlertid ikke virke fordi year brukes i beregningen av maxday, mens variablene i let-uttrykket ikke er bundet før i uttrykkets kropp. 136

stedet må vi legge et let-uttrykk til rundt det innerste slik: (let ((year (quotient n 10000)) (let ((day (remainder n 100)) (maxday <beregn-maxday>) (and (>= day 1) (<= d maxday) (list day month year)))))))...) Eller vi kan bruke let*. (let* ((year (quotient n 10000)) (day (remainder n 100)) (maxday <beregn-maxday>) (and (>= day 1) (<= d maxday) (list day month year))))))) 137

Med let* kan vi forsåvidt legge alle variablene i funksjonen inn i én og samme liste. I stedet for å sjekke månedslengden før alt annet, lar vi cond-utrrykket for beregning av maxday returner 0, for ulovlig måned, slik at alle dagnumre blir ulovlige. (define (eight-digits->date-triple n) (let* ((month (remainder (quotient n 100) 100)) (year (quotient n 10000)) (day (remainder n 100)) (maxday (cond ((or (= month 1) (= month 3) (= month 5) (= month 7) (= month 8) (= month 10) (= month 12)) 31) ((or (= month 4) (= month 6) (= month 9) (= month 11)) 30) ((= month 2) (if (leapyear? year) 29 28)) (else 0)))) (and (>= d 1) (<= d maxday) (list day month year)))) 138

Noen flere eksempler på bruk av lambda En klassiker: (define (make-adder num) (lambda (x) (+ x num))) ((make-adder 4) 7) 11 Vi bruker substitusjon for å se hva som foregår: (make-adder 4) (lambda (x) (+ x 4)) ((make-adder 4) 7) ((lambda (x) (+ x 4)) 7) (+ 7 4) 11 ((lambda (y) ((lambda (x) (+ x y)) 7)) 4)) 11 Med let ser det slik ut. (let ((y 4) (let (x 7) (+ x y)) 11 139

Hvilke navn gir mening hvor og når? I definisjonen av make-adder gir x mening i lambda-utrykkets kropp, og bare der, mens num gir mening i den ytre funksjonskroppen og dermed også i lambda-utrykkets kropp. I neste omgang, når vi kaller make-adder for å få laget en prosedyre, sender vi et aktuelt argument på den plassen num angir. Dette bindes til num, i definisjonen av resultatprosedyren. Resultatprosedyren tar ett argument, angitt ved variabelen x. I siste omgang, når vi kaller resultatprosedyren, dvs. resultatet av kallet på make-adder, binder vi x til det aktuelle argumentet og erstatter x med dette under utførelsen av kallet. 140

Den prosedyren make-adder returnerer, la oss kalle den p, har med seg den omgivelsen den returneres fra, der num er bundet til argumentet til make-adder. Denne omgivelsen er utilgjengelig for alle andre enn p. (sml et objekts private felt i Java) Når p kalles opprettes det en omgivelse "innenfor" omgivelsen til p, der x bindes til argumentet til p, og uttrykket (+ x num) evalueres. (define (make-adder num) (lambda (x) (+ x num))) (make-adder 4) num = 4 (lambda (x) (+ x num)) num = 4 ((make-adder 4) 7) x = 7 (+ x num)) 141

Et annet eksempel: (define (flip proc) (lambda (a b) (proc b a))) Argumentet til flip skal være en prosedyre som tar to argumenter, f.eks. subtraksjonsprosedyren -, og returnerer en prosedyre som bytter rekkefølgen på argumentene til argumentprosedyren under utførelsen. (- 5 2) 3 ((flip -) 5 2) -3 (/ 3 6) 1/2 ((flip /) 3 6) 2 (> 3 6) #f ((flip >) 3 6) #t ((flip string-append) "tine" "kan") "kantine" 142

Litt om tall Naturlige tall De positive heltallene {1, 2, 3,...}. Noen regner også 0 som et naturlig tall. Vi angir for mengden av naturlige tall. Heltall (integer) {..., -2, -1, 0, 1, 2,...} Vi angir (tysk Zahl) for mengden av heltall. Et heltall n kan være negativt (n - ), null (n = 0), positivt (n + ) eller nonnegativt (n * = + {0}). Rasjonale tall (fra latin ratio som bl.a betyr utregning her: (utregning av) forholdet mellom to tall) Et tall som kan uttrykkes som en brøk p/q der p og q er heltall og q 0, kalles et rasjonalt tall med teller (numerator) p og nevner (denominator) q. Vi angir for mengden av rasjonale tall. De rasjonale tallene kan, i likhet med heltallene, telles, dvs. oppramses (enumeres). (søk på countable numbers) (At en mengde er tellbare betyr ikke at vi alltid kan finne ut hvor stor den er ved å telle dens elementer. En mengde kan være både tellbar og uendelig.) Det er alltid mulig å finne et rasjonaltt tall mellom hvilke som helst to rasjonale tall, Vi sier at de rasjonale tallene er tette, noe heltallene ikke er, men ikke kontinuerlige, slik de reelle tallene er. 143

Irrasjonale tall = ikke-rasjonale tall (eg. ikke utregnbare). Noen av disse som e, og 2 er mer kjent enn andre Reelle tall Unionen av de rasjonale og de irrasjonale tall, angitt ved symbolet. Mengden av reell tall kalles også kontinuum (el. kontinuet el. kontinuumet), angitt ved symbolet c. Komplekse og imgainære tall De reelle tall kan utvides ved tilføyelsen av det imaginære tallet i = -1. Tall på formen x + iy, der x and y begge er reelle tall, kalles komplekse tall. Transcendentale tall er tall som ikke er løsning av noe polynom (x er løsningen av et polynom hvis a 0 + a 1 x + a 2 x 2 +... + a n x n = 0). Mest kjent er og e. 144

Telling av rasjonale tall Vi kan anskueliggjøre som en matrise der radene har formen 1/n, 2/n, 3/n, og kolonnene har formen n/1, n/2, n/3,. Men hvordan teller vi tallene i matrisen, når radene og kolonnene hver for seg er uendelige? 1/1 2/1 3/1 4/1 5/1 6/1 7/1 8/1... 1/2 2/2 3/2 4/2 5/2 6/2 7/2 1/3 2/3 3/3 4/3 5/3 6/3 1/4 2/4 3/4 4/4 5/4 1/5 2/5 3/5 4/5 1/6 2/6 3/6 1/7 2/7 1/8 Dette gir alle tallene i, men det gir også en masse tall vi ikke ønsker å ha med, dvs. : vil vil bare ha med de tall x/y der x og y er relativt prime (hvilket vil si at gcd(x, y) = 1). 145

Lukning i algebra De naturlige tall er lukket under addisjon og multiplikasjon, idet enhver sum s = a + b og ethvert produkt p = ab, der a og b er naturlige tall, er selv naturlige tall. Heltallene er også lukket under subtraksjon, og de rasjonale tallene er dessuten lukket under divisjon. De relle tallene har samme lukningsegenskaper som rasjonale. De komplekse tallene er dessuten lukket under rotutrekning. En delmengde vil ofte mangle noen av lukningsegenskapene til sin supermengde, f.eks. er heltallene, som er en delmengde av de rasjonale tallene, ikke lukket under divisjon. Dette gjelder også andre delmengder, f.eks. odde-tallene som er en delmengde avd heltallene (og hva er lukningsegenskapene for disse?). 146

Lukning i algebra nevnes bl.a. for å unngå forveksling med lukning, closure, i betydningen innpakning av en prosedyre sammen med dennes omgivelser, slik at enhver instans av prosedyren har med seg variablene i disse omgivelser, også utenfor det stedet der prosedyren er definert. Forfatterne av SICP mener at Begrepet lukning bør være reservert for algebra. 147

Dataabstraksjoner Vi gjorde tidligere et poeng av at prosedyrene i et program inngår i et hierarkisk system der vi kan gå inn på de ulike nivåer og gi fullstendige beskrivelser av hva som foregår, ut fra en trygghet om at prosedyrene på lavere nivåer virker etter sin hensikt. Dette er én side ved abstrahering bl.a kalt black-box-abstrahering. Vi skal nå knytte begrepet asbtrahering nærmere til begrepet datatype. Vi skal bl.a. se at det går an å snakke utfyllende om aritmetiske talloperasjoner, uten å ha noe begrep om hvordan heltall er representert på dypere nivåer, og vi skal anskueliggjøre dette vha. ulike representasjoner av heltall. Vi starter imidlertid med å se på rasjonale tall brøker (fractions), om man vil. 148

Vi går ut fra at et rasjonalt tall på en eller annen måte er satt sammen av to hele tall kalt numerator (teller angir antall deler) og denominator (nevner angir del-størrelsen). Dette uttrykker vi i Scheme vha en konstruktur-prosedyre og to selektor-prosedyrer: (define (make-rat x y) <det rasjonale tallet hvis numerator = x og denominator = y>) (define (numer x) <numeratoren til det rasjonale tallet x>) (define (denom x) <denominatoren til det rasjonale tallet x>) 149

Vi ønsker å definere operasjonene addisjon, subtraksjon, multiplikasjon og divisjon for rasjonale tall (jfr. disses lukningsegenskaper). Uavhengig av representasjonsmåten skal følgende aritmetiske forhold gjelde: Eksempler a c a d + c b 4 2 4 3 + 2 5 22 + = (1) + = = b d b d 5 3 5 3 15 a c a d - c b 4 2 4 3-2 5 2 - = (2) + = = b d b d 5 3 5 3 15 a c a c 4 2 4 2 8 = (3) = = b d b d 5 3 5 3 15 a 4 b a d 5 4 3 12 6 = (4) = = (= ) c c b 2 2 5 10 5 d 3 a c = a d = c b (5) b d 150

Dette uttrykker vi i Scheme ved følgende prosedyrer (define (add-rat x y) ; se (1) (make-rat (+ (* (numer x) (denom y)) (* (numer y) (denom x))) (* (denom x) (denom y)))) (define (sub-rat x y) ; se (2) (make-rat (- (* (numer x) (denom y)) (* (numer y) (denom x))) (* (denom x) (denom y)))) (define (mul-rat x y) ; se (3) (make-rat (* (numer x) (numer y)) (* (denom x) (denom y)))) (define (div-rat x y) ; se (4) (make-rat (* (numer x) (denom y)) (* (denom x) (numer y)))) (define (equal-rat? x y) ; se (5) (= (* (numer x) (denom y)) (* (numer y) (denom x)))) 151

Merk at vi har innført og brukt, men ennå ikke implementert, konstruktoren make-rat og selektorene numer og denom. Men ovenstående må gjelde uansett hvordan disse implementeres. Én implementasjon går ut på å bruke det fundamentale Lisp-begrepet par. Et par konstrueres ved prosedyren cons, og elementene selekteres vha. hhv. car og cdr. (cons 1 2) (1. 2) (define par (cons 1 2)) ; konstruktor (car par) 1 ; selektor for første del av paret (cdr par) 2 ; selektor for andre del av paret Vha. disse primitivene kan vi definere konstruktorene og selekteorene for rasjonale tall slik: (define (make-rat x y) (cons x y)) (define (numer x) (car x)) (define (denom x) (cdr x)) 152 cons er en forkortelse for construct, car er en forkortelse for Contents of the Address part of Register number cdr er en forkortelse for Contents of the Decrement part of Register number

Vi kan også innfører en dedisert utskriftsprosedyre for rasjonale tall vha. den generelle utskriftsprosedyren display. (define (print-rat x) (newline) (display (numer x)) (display "/") (display (denom x))) newline gir linjeskift. display tar ett argument av en hvilken som helst type og skriver ut dettes verdi. (define one-third (make-rat 1 3)) I motsetning til REPL, skriver display ut strenger uten anførselstegn rundt dem. (print-rat one-third) 1/3 Utskriftsprosedyren skriver ut numerator og denominator hver for seg, uten hensyn til forholdet mellom dem. 153

Eks: (add-rat one-third one-third) => (6. 9) (Se implementasjonene på side 128) (print-rat (add-rat one-third one-third)) 6/9 Som vi ser kan denne brøken reduseres. (display (+ (/ 1 3) (/ 1 3))) 2/3 Dette kan vi også få til vha. primitiven gcd som tar to eller flere tall som argumenter og returneres disses største felles divisor (greatest common divisor). (gcd 420 378) 42 420 = 2 2 3 5 7. 378 = 2 3 3 3 7. Felles: 42 = 2 3 7. 42 deler både 420 og 378, og det finnes ikke noe tall større enn 42 som deler både 420 og 378. 378 378/42 9 Vi får da = = 420 420/42 10 154

Vi kan enten (a) bruke gcd i konstruktoren make-rat (define (make-rat x y) (let ((g (gcd x y) (cons (/ x g) (/ y g)))) eller (b) i selektorene numer og denom. Mer kompakt (define (numer x) (define (numer x) (let ((g (gcd (car x) (cdr x)))) (/ (car x) (gcd (car x) (cdr x)))) (/ (car x) g))) (define (denom x) (define (denom x) (let ((g (gcd (car x) (cdr x)))) (/ (cdr x) (gcd (car x) (cdr x)))) (/ (cdr x) g))) På det abstraksjonsnivået der våre rasjonaltallsprosedyrer brukes, er det ingen semantisk forskjell mellom (a) og (b). Men det kan være en effektivitetsmessig forskjell, som er uten interesse i denne sammenhengen. 155

Abstraksjonsbarrierer De fleste objektorienterte språk har syntaktiske mekanismer for å skille mellom et objekts - private (skjulte beskyttede) egenskaper - typisk datafelt, og metoder som kan endre disse, og - alment tilgjengelige - typisk metoder som gir tilgang til visse data, eller utfører beregninger eller andre oppgaver. I Scheme-programmering har vi ingen syntaks for slike skiller, men vi insisterer på visse konvensjoner. I henhold til disse implementerer vi våre datatyper ved et sett prosedyrer som typisk omfatter - et predikat (eller flere) - en konstruktor (eller flere) og - noen selektorer. 156

Brukeren av typen får signaturene 1 til, men ikke implementasjonene av, disse prosedyrene. Eks.1: type par Eks.2: type rasjonal predikat pair? predikat rational? ;* konstruktor cons konstruktor make-rat selektorer car cdr selektorer numer denom Her representer Eks.2 en abstraksjonsbarriere mot brukeren av våre prosedyrer for rasjonale tall, i den forstand at brukeren må kjenne prosedyrenes navn, sammenligner men ikke trenger å vite hvordan prosedyrene er implementert. I forhold til omverdene, er det eneste interessante at equal-rat? * Det finnes en Scheme-primitiv med navnet rational?, men her må en eventuell implementasjon være vår egen, i samsvar med de øvrige implementasjonene på samme nivå. Det er imidlertid ingenting i veien for å implementere vår egen rational? vha. primitiven rational? gitt x = (make-rat n d), ; når n og d er heltall, så garanterer vi at (numer x) / (denom x) = n/d. 1 Ikke helt som i Java. Siden Scheme er dynamisk typet, må paramater- og returtypene angis i dokumentasjonen 157

Par Vi kan generalisere dette til at en datatype er gitt ved - ett sett med prosedyrer for konstruksjon og seleksjon og - ett sett med betingelser som disse prosedyrer må tilfredstille. En overraskende implementasjon av par Her følger en konstruktor og to selektorer som tilfredsstiller vår forpliktelse overfor bruker hva angår par. (define (cons x y) (define (dispatch message) (cond ((= message 0) x) ((= message 1) y) (else (error "Argument must be 0 or 1 CONS" message)))) dispatch) (define (car z) (z 0)) (define (cdr z) (z 1)) 158

Vi legger for det første merke til at returverdien fra cons er en én-arguments prosedyre nærmere bestemt den lokale prosedyren dispatch (formidle, send videre) Dernest merker vi oss at i kroppen til hver av de to selektorene car og cdr opptrer den formelle parameteren z som en prosedyre, i kroppen til car kalles z med 0 som argument i kroppen til cdr kalles z med 1 som argument Prosedyren z er en instans av den lokale dispatch-prosedyren i cons. Denne har hele tiden med seg de omgivelsene den ble skapt innenfor. Her omfatter omgivelsene Sammenlign parametrene x og y med bundet til de verdiene disse fikk ved make-adder det kallet som førte til at prosedyren ble instansiert. på side 141. 159

Med ovenstående implementasjon av konstruktoren cons og selektorene car og cdr kan vi definere et par med nøyaktig samme semantikk som tidligere (s. 151): (define par (cons 1 2)) ; par er nå et prosedyreobjekt med en omgivelse der x og y er bundet til hhv. 1 og 2 (car par) 1 ; car kalles med prosedyreobjektet par som argument (cdr par) 2 ; car kalles med prosedyreobjektet par som argument Men legg igjen merke til at med denne implementasjonen av cons returneres en prosedyre ikke et par på formen (x. y). (cons 1 2) #<procedure:formidle> NB! Ovenstående viser ikke noe som helst om hvordan par faktisk er implementert i Scheme bare at vi kunne ha implementert par på denne måten fordi prosedyrer er førsteklasses objekter. 160

En omskriving av cons-implementasjon vha. lambda Vi kan skrive om den ovennevnte implementasjonen av cons vha. lambda. Siden dispatch-prosedyren ikke kalles i konstrukturen, trenger den heller ikke noe navn der, og vi kan returnere en anonym prosedyre direkte. (define (cons x y) (lambda(message) (cond ((= message 0) x) ((= message 1) y) (else (error "Argument must be 0 or 1 CONS" message))))) 161

Hierarkier og lukningsegenskaper Par er en svært nyttig og effektiv byggesten for konstruksjon av praktisk talt alle de sammensatte datatyper en kan tenke seg. I SICP brukes følgende grafiske representasjon av par. Som vi ser kan vi bruke cons til å lage par hvis elementer er par hvilket vil si at par er lukket under cons. 162

Dette gjør det mulig å skape hierarkiske datastrukturer trær. sykdomsbehandling medisinsk fysioterapeutisk alternativt kirurgisk medikamentelt massasje trening religiøst healing kniv laser profylaktisk ad hoc omvendelse forbønn snåsisk '(sykdomsbehandling (medisinsk (kirurgisk (kniv laser)) (medikamentelt (profylaktisk ad-hoc))) (fysioterapeutisk (massasje trening)) (alternativt (religiøst (omvendelse forbønn)) (healing (snåsisk)))) På neste side ser vi hvordan ovenstående kan konstrueres vha. cons. Der bruker vi følgende kode for a lage et par med et tomt andre-element: (cons 1 '()) ==> (1) Den enkle apostrofen gjør det mulig å bruke variabler (symboler) som ikke er definert. 163

(cons 'sykdomsbehandling (cons (cons 'medisinsk (cons (cons 'kirurgisk (cons (cons 'kniv (cons 'laser '())) '())) (cons (cons 'medikamentelt (cons (cons 'profylaktisk (cons 'ad-hoc '())) '())) '()))) (cons (cons 'fysioterapeutisk (cons (cons 'massasje (cons 'trening '())) '())) (cons (cons 'alternativt (cons (cons 'religiøst (cons (cons 'omvendelse (cons 'forbønn '())) '())) (cons (cons 'healing (cons (cons 'snåsisk '()) '())) '()))) '())))) 164

Lister Før vi ser nærmere på trær, skal vi ta for oss den enkelere listestrukturen. (cons 1 (cons 2 (cons 3 (cons 4 '())))) (1 2 3 4) NB! I læreboken brukes verdien nil som synonym for den tomme listen representert grafisk ved boksen med diagonal strek. Ellers angis den tomme listen med en tom parentes prefikset med en enkel apostrof: '(), slik som i figuren over, og vi skal for vår del stort sett bruke denne varianten (uten å problematisere bruken av ' i denne omgang). NB! Verdien nil er ikke definert i R 5 RS. 165

For å forenkle konstruksjonen av lister har Scheme primitiven list. (list 1 2 3 4) (cons 1 (cons 2 (cons 3 (cons 4 '())))) Merk at denne ekvivalensen innebærer at også følgende ekvivalenser gjelder: (list 1 2 3 4) (cons 1 (list 2 3 4)) (cons 1 (cons 2 (list 3 4))) slik at f.eks. (car (list 1 2 3 4)) 1 (cdr (list 1 2 3 4)) (2 3 4) (car (cdr (list 1 2 3 4))) 2 (cdr (cdr (list 1 2 3 4))) (3 4) (car (cdr (cdr (list 1 2 3 4)))) 3 (cdr (cdr (cdr (list 1 2 3 4)))) (4) (car (cdr (cdr (cdr (list 1 2 3 4))))) 4 (cdr (cdr (cdr (cdr (list 1 2 3 4))))) '() Siden det er nokså vanlig å kombinere de to selektorene på alle mulige måter, finnes det egne biblioteksprosedyrer på formen cxr, der X kan være fra 2 til 4 kombinasjoner av a-er og/eller d-er (caar, cadr,, cdddar, cddddr) 166

Vha. list kan sykdomsbehandlingstreet konstrueres slik: (list 'sykdomsbehandling (list 'medisinsk (list 'kirurgisk (list 'kniv 'laser)) (list 'medikamentelt (list 'profylaktisk 'ad-hoc))) (list 'fysioterapeutisk (list 'massasje 'trening)) (list 'alternativt (list 'religiøst (list 'omvendelse 'forbønn)) (list 'healing (list 'snåsisk)))) 167

168

Flere dataabstraksjoner Tall En ukeoppogave går ut på å implementere heltall på ulike måter. Her dreier det som tre nivåer: (a) abstraksjonen av heltallene mht. de operasjoner under hvilke de er lukket, (b) den absktrakte implementasjonen disse operasjonen, og (c) den konkrete implementasjonen av disse operasjonene mht. deres fysiske / visuelle / symbolske representasjon. 169

For lukningsegenskapene, nivå (a), bruker vi succ og pred fra nivå (b) til å definerer funksjonene add og mul, for hhv. addisjon og multiplikasjon som er de operasjonene heltallene er lukket under På nivå (b) ser vi hen til at heltallene er tellbare, og ordnet, slik at (sammen med subtraksjon, som vi ikke bryr oss med i her). det ene tallet følger etter det andre f.eks. slik at 3 følger etter 2. Vi kan dermed innføre etterfølgerfunksjonen inc og increment forgjengerfunksjonen dec decrement som vi definerer vha. de repreresentasjonsavhengige basisprosedyrene på nivå (c). 170

På nivå (c) definere vi etterfølgerfunksjonen med henblikk på hvordan tallene er representert, som en linje med enkeltvise (discrete) punkter, ordnede rekker med sifre, etc. Implemnterer vi succ og pred vha. Scheme-primitivene + og (se oppgave 1.9, s. 36), vil nivå (c) bestå av implementasjonen av disse i Scheme selv. En annen mulig representasjon er rekker av tomme parenteser der etterfølgeren til f.eks. ()() er ()()(), eller et nøste av parentester der etterfølgeren til (()) er ((())). Her kan vi på nivå (c) bruke typen par med konstruktoren cons og selektorene car og cdr. 171

Tall generelt (define (plus x y) (if (zero? x) y (inc (plus (dec x) y)))) Nedenstående er svar på en ukeoppgave, men det skader ikke at dere har sett løsningen før, så lenge dere ikke titter under arbeidet med løsningen (define (times x y) (if (zero? x) 0 (plus (times (dec x) y) y))) Sifrede tall (define (inc x) (+ x 1)) (define (dec x) (- x 1)) (define number? number?) ; Scheme primitve 172

Brede tall (define zero? null?) (define zero '()) (define (inc x) (cons '() x)) (define (dec x) (cdr x)) (define (number? x) (or (zero? x) (and (zero? (car x)) (number? (cdr x))))) Noen brede tall (define x (inc (inc (dec (inc (inc zero)))))) ; (() () ()) (define y (dec (inc (dec (inc (inc (inc zero))))))) ; (() ()) (define z (inc (inc (inc y)))) ; (() () () ()) (plus x y) (() () () () ()) (times y z) (() () () () () () () ()) 173

Dype tall (define zero? null?) (define zero '()) (define (inc x) (cons x '())) ; ==> (x), dvs. lista med x som første og eneste element (define (dec x) (car x)) ; ==> første (og eneste) element i x. (define (number? x) (or (null? x) (and (null? (cdr x)) (tall? (car x))))) Noen dype tall (define x (inc (inc (dec (inc (inc zero)))))) ; (((()))) tilsvarer sifret ta 3 (define y (inc (inc x))) ; (((((()))))) tilsvarer sifret ta 4 (plus x y) ((((((((())))))))) ; tilsvarer sifret ta 4 (times x y) (((((((((((((((()))))))))))))))) ; tilsvarer sifret ta 12 174

175

176