Ohjelmoinnin peruskurssien laaja oppimäärä Luento 2: SICP kohdat 22.2.3 Riku Saikkonen 2. 11. 2010
Sisältö 1 Linkitetyt listat 2 Listaoperaatioita 3 Listarakenteet 4 Gambit-C:n Scheme-debuggeri
Linkitetyt listat (SICP 2.1.1, 2.2.1) funktionaalinen ohjelmointitapa perustuu usein laskemiseen linkitettyjen listojen käsittelyfunktioilla Schemessä (cons a b) tekee parin, jonka avulla muodostetaan listoja (car p) palauttaa parin ensimmäisen alkion (cdr p) toisen (eli loppulistan) linkitetty lista (list) on jono pareja, joka päättyy tyhjään listaan nil (tai '()): (cons 1 (cons 2 (cons 3 nil))) (1 2 3) listoja voi tuottaa myös näin: (list 1 2 3) ja '(1 2 3) huomaa, että (1 2 3) on tapa, jolla listat tulostetaan, mutta koodina se yrittäisi kutsua 1-nimistä proseduuria
Schemen ja Javan listat Javan List-luokassa on myös linkitetty lista, samoin kuin useimmissa muissa kielissä Schemen listat ovat rakenteeltaan mahdollisimman yksinkertaisia jotta niitä voisi käyttää kätevästi esim. rekursiivisesti niiden päälle voi rakentaa monimutkaisempia tietorakenteita esimerkiksi lista on vain yhteen suuntaan linkitetty, jotta osalistoja voisi antaa eteenpäin muille funktioille samanlaiset listat (ja samat listankäsittelyoperaatiot) on muissakin funktionaalisissa kielissä Schemen listoja on tapana käyttää enimmäkseen funktionaalisesti (tehdään uusi lista eikä muuteta olemassaolevaa)
Teoriassa listoja ei tarvittaisi (SICP 2.1.3) periaatteessa listoja ei tarvittaisi kieleen sisäänrakennettuna ominaisuutena, vaan ne (ja muutkin vastaavat tietorakenteet) voisi rakentaa ensimmäisen luokan funktioista toteutukseksi käyvät mitkä tahansa proseduurit cons, car ja cdr, jotka toteuttavat alla olevat ehdot (sekä tyhjä lista ja null?) käytännössä tämä ei toki olisi kovin tehokasta... Listojen toteutus proseduureina (SICP tehtävästä 2.4) (define (cons x y) ; x ja y jäävät talteen palautettuun proseduuriin (lambda (m) (m x y))) (define (car z) ; toteuttaa ehdon (car (cons a b)) = a (z (lambda (p q) p))) (define (cdr z) ; toteuttaa ehdon (cdr (cons a b)) = b (z (lambda (p q) q))) (define nil 999) ; mikä tahansa tunnistettava vakioalkio käy Testiajo: (car (cdr (cons 1 (cons 2 (cons 3 nil))))) 2
Sisältö 1 Linkitetyt listat 2 Listaoperaatioita 3 Listarakenteet 4 Gambit-C:n Scheme-debuggeri
Muutamia listaoperaatioita (SICP 2.2.1, 2.2.3) (length l) alkioiden määrä (list-ref l i) tietty alkio (i= 0 ensimmäinen) (map f l) tekee (a b c):sta (f (a) f (b) f (c)):n (filter p l) palauttaa listan, jossa on l:stä vain alkiot x, joille (p x) on tosi muitakin on, erityisesti funktionaalisessa ohjelmoinnissa... seuraavalla luennolla lisää tästä, mm. accumulate Esimerkkejä (length (list 1 2 3)) 3 (filter odd? (list 1 2 3 4 5)) (1 3 5) (map length (list (list 1 2) (list 3) (list 4 5 6))) (2 1 3)
Lisää listaoperaatioita (SICP 2.2.1, 2.2.3) (reverse l) lista toisinpäin (append l1 l2) listat peräkkäin (voi olla enemmänkin kuin 2) (list-tail l k) ensimmäiset k alkiota pois (pair? l) onko l pari? (vakioaikainen) (list? l) onko l tyhjään listaan päättyvä lista? (käy l:n läpi) (apply f l) kutsuu f:ää kerran argumentteina listan alkiot (for-each f l) kutsuu f:ää yksi kerrallaan kaikille listan alkioille Esimerkkejä (reverse (list 1 2 3 4)) (4 3 2 1) (append (list 1 2) (list 3 4)) (1 2 3 4) (cons (list 1 2) (list 3 4)) ((1 2) 3 4) (list-tail (list 1 2 3 4) 2) (3 4) (apply + (list 1 2 3 4)) 10 eli (+ 1 2 3 4)
Listaoperaatiot olisi helppo määritellä itse (SICP 2.2.1, 2.2.3) Esimerkkeinä list-ref ja filter (define (list-ref items n) (if (= n 0) (car items) (list-ref (cdr items) (- n 1)))) (define (filter predicate sequence) (cond ((null? sequence) nil) ((predicate (car sequence)) (cons (car sequence) (filter predicate (cdr sequence)))) (else (filter predicate (cdr sequence))))) filter ei kuulu standardi-schemeen, joten sen joutuu määrittelemään itse
Sisältö 1 Linkitetyt listat 2 Listaoperaatioita 3 Listarakenteet 4 Gambit-C:n Scheme-debuggeri
Listarakenteet (SICP 2.2.1, 2.2.2) sisäkkäiset listat sekä listat, jotka eivät pääty nil-alkioon, muodostavat listarakenteen (list structure) lista on listarakenne, joka päättyy tyhjään listaan sisäkkäiset listat toimivat luontevasti: (list (list 1 2 3) (list 4 5) (list 6)) ((1 2 3) (4 5) (6)) listarakenne, joka ei pääty tyhjään listaan, näytetään näin: (cons 1 (cons 2 3)) (1 2. 3) siis pisteen. jälkeen tulostetaan yksi alkio eli viimeisen parin cdr toinen selitys tulostamiselle: (cons a b) tulostetaan (a. b), paitsi (cons a nil) tulostetaan (a) (cons a lista ) tulostetaan (a lista tulostettuna ) ilman näitä lyhennysmerkintöjä (cons 1 (cons 2 nil)) olisi (1. (2. ())) eikä (1 2)
Listarakenteen tulostus koodina Listarakenteen tulostava Scheme-koodi (define (print-contents l) (print-list-structure (car l)) (cond ((null? (cdr l)) 'done) ((not (pair? (cdr l))) (display ". ") (print-list-structure (cdr l))) (else (display " ") (print-contents (cdr l))))) (define (print-list-structure l) (cond ((null? l) (display "()")) ((not (pair? l)) (display l)) (else (display "(") (print-contents l) (display ")")))) ; apufunktio Tulostusesimerkkejä: (list (list 1 2) (list 3 4)) ((1 2) (3 4)) (cons (list 1 2) (list 3 4)) ((1 2) 3 4) (list (cons 1 2) (cons 3 4)) ((1. 2) (3. 4)) (cons (cons 1 2) (cons 3 4)) ((1. 2) 3. 4)
Sisältö 1 Linkitetyt listat 2 Listaoperaatioita 3 Listarakenteet 4 Gambit-C:n Scheme-debuggeri
Debuggeriin pääseminen Scheme-tulkista Scheme-lauseke (begin (step) (foo)) pysähtyy debuggeriin juuri ennen lausekkeen (foo) suoritusta Ctrl-C (Emacs: C-c C-c) kesken pitkän suorituksen pysähtyy myös debuggeriin samoin virheilmoitus, esim. (/ 1 0) keskellä koodia (break foo) muokkaa päätason proseduuria foo niin että juuri ennen sen kutsua pysähdytään debuggeriin; (unbreak foo) kumoaa debuggeritilan tunnistaa tulkin kehotteesta: > ei debuggeria, 1>, 2> jne. debuggerissa numero kertoo monennessako sisäkkäisessä (!) debuggerissa ollaan
Debuggerikomentoja Ctrl-D tai,d poistuu päällimmäisestä debuggerista,t poistuu kokonaan debuggerista takaisin tulkkiin,? näyttää komentolistauksen,b näyttää kutsupinon,be näyttää kutsupinon muuttujineen, numero menee kutsupinon tiettyyn kohtaan,c jatkaa suoritusta normaalisti,(c X) jatkaa suoritusta virheen jälkeen: virheen aiheuttanut lauseke palauttaa X,s jatkaa lyhyen askelen eteenpäin,l tekee funktiokutsun ja pysähtyy debuggeriin sen jälkeen ja debuggerissa voi suorittaa Scheme-lausekkeita normaalisti (esim. katsoa muuttujan arvoa kirjoittamalla sen nimi)