Ohjelmoinnin peruskurssien laaja oppimäärä Luento 5: Sijoituslause, SICP-oliot, tietorakenteen muuttaminen (mm. SICP 33.1.3, 3.33.3.2) Riku Saikkonen 6. 11. 2012
Sisältö 1 Muuttujan arvon muuttaminen: set! 2 SICP-oliot 3 Tietorakenteen muuttaminen: set-car! ja set-cdr!
Sijoituslause (SICP 3.1) (set! muuttuja lauseke ) laskee lausekkeen ja sijoittaa sen arvon muuttujan uudeksi arvoksi sama muuttuja kuin mihin pelkkä muuttujan nimi koodissa viittaa muut samaan arvoon osoittavat muuttujat eivät muutu Esimerkkejä (define x 1) (define y x) (set! x (+ x 1)) ; nyt x = 2, y = 1 (define (f z) (set! z (+ z 1)) x) (f x) ; palauttaa 2; edelleen x = 2, y = 1 (let ((x 5)) (set! y x) ; nyt y = 5 (set! x (+ x 1)) x) ; palauttaa 6, globaali x = 2, y = 5
Mikä on muuttuja? muuttuja on arvolle annettu nimi toinen tapa ajatella: muuttuja on osoitin arvoon (kaikki arvot, myös esim. kokonaisluvut, ovat periaatteessa tallessa jossain muistissa, ja muuttuja vain osoittaa johonkin niistä) mutta muuttuja ei ole arvon tallennuspaikka (define y x) (set! y (+ y 1)) ei muuta x:ää ei myöskään jos x osoittaa esim. listaan Esimerkkejä (define x (list 1 2)) (define y x) ; x = (1 2), y = (1 2) (set! y (cdr y)) ; x = (1 2), y = (2) (set! x (list 3 4)) ; x = (3 4), y = (2) (define (f l) (set! l (cdr l)) (car l)) (f x) ; palauttaa 4; edelleen x = (3 4), y = (2)
Sijoituslauseen haittoja (SICP 3.1.3) sijoituslauseella on sivuvaikutus: se muuttaa ohjelman tilaa eikä vain palauta laskemaansa arvoa samoin tulostuksella eli display:llä (ulospäin näkyvä tila muuttuu) (Schemen set!:llä on periaatteessa paluuarvo, mutta toteutus saa valita sen; käytännössä tulkki jättää sen useimmiten tulostamatta) sivuvaikutuksista seuraa mm: tarve suorittaa useampi lauseke peräkkäin (mm. begin) ohjelman toimintaa on vaikeampi mallintaa matemaattisesti: pitää käsitellä muuttujien vanhoja ja uusia arvoja ohjelmien laskujärjestys on usein tarkemmin määrätty (kääntäjällä on vähemmän mahdollisuuksia optimoida sitä) ohjelman rinnakkainen suorittaminen on vaikeampaa sijoituslauseen hyviä puolia: joitain algoritmeja on vaikea toteuttaa ilman sitä joskus se auttaa modulaarisuudessa: sillä voi piilottaa sisäistä tilaa sijoituslauseita käyttävä silmukka vastaa koneen todellista toimintaa suoremmin kuin esim. häntärekursio
Sisältö 1 Muuttujan arvon muuttaminen: set! 2 SICP-oliot 3 Tietorakenteen muuttaminen: set-car! ja set-cdr!
SICP-tyyliset oliot (SICP 3.1.1) Schemessä ei ole varsinaisia olioita, mutta samankaltaisen rakenteen voi tehdä palautetulla proseduurilla Luokka, jossa on kaksi metodia acct1.scm (define (make-account balance) (define (withdraw amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")) (define (deposit amount) (set! balance (+ balance amount)) balance) (define (dispatch m) (cond ((eq? m 'withdraw) withdraw) ((eq? m 'deposit) deposit) (else (error "Unknown request -- MAKE-ACCOUNT" m)))) dispatch) ; make-account siis palauttaa dispatch-proseduurin (define acc (make-account 100)) ((acc 'withdraw) 50) 50 ((acc 'deposit) 40) 90 ; kutsu luo uuden olion
Muunnelma helpommalla metodikutsusyntaksilla Sama luokka kuin edellä (erilainen dispatch) acct2.scm (define (make-account balance) (define (withdraw amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")) (define (deposit amount) (set! balance (+ balance amount)) balance) (define (dispatch m. args) ; args = lista lopuista argumenteisa (cond ((eq? m 'withdraw) (apply withdraw args)) ((eq? m 'deposit) (apply deposit args)) (else (error "Unknown request -- MAKE-ACCOUNT" m)))) dispatch) (define acc (make-account 100)) (acc 'withdraw 50) 50 (acc 'deposit 40) 90
Vaihteleva määrä argumentteja Schemessä on pieni lisäominaisuus proseduurien argumentteja määriteltäessä: jos argumenttilista loppuu. x, proseduuria kutsuttaessa muuttujaan x tulee lista kaikista lopuista kutsussa annetuista argumenteista Pythonissa vastaava on *, esim. def f(a, b, *c): edellisen kalvon dispatch käytti tätä Esimerkki: osa argumenteista (define (f a b. c) c) ;; tai yhtä hyvin lambdalla: (define f (lambda (a b. c) c)) (f 1 2 3 4) (3 4) (f 1 2 3) (3) (f 1 2) () (f 1) virhe! Esimerkki: kaikki argumentit (define (g. x) x) ;; tai: (define g (lambda x ; ei siis (. x) x)) (g 1 2) (1 2) (g 1) (1) (g) ()
SICP-olioista olion tila on tallessa palautetussa dispatch-proseduurissa (sen näkemissä paikallisissa muuttujissa) muuttujien näkyvyys: kentät ovat Java-termein private, metodit myös, paitsi että dispatch-proseduuri tekee niistä public:eja samaan ideaan voisi lisätä: enemmän paikallisia muuttujia eli kenttiä static-kenttiä eli konstruktoriproseduurin ulkopuolella määriteltyjä muuttujia (Javan static-metodi = globaali proseduuri) metodit voivat kutsua itseään ja toisiaan (dispatch vastaa this:iä/self:iä) dispatch-proseduurissa voisi tarjota useamman rajapinnan samoihin metodeihin edellä ollut ratkaisu ei ole täydellistä olio-ohjelmointia: mm. perintä puuttuu (tästä lisää myöhemmin... )
Sisältö 1 Muuttujan arvon muuttaminen: set! 2 SICP-oliot 3 Tietorakenteen muuttaminen: set-car! ja set-cdr!
set-car! ja set-cdr! (SICP 3.3.1) parien (ja siis listojen) osia voi muuttaa set-car!:lla ja set-cdr!:llä vastaavasti merkkijonoja string-set!:llä, vektoreita vector-set!:llä jne. muutos näkyy kaikkialle, joka viittaa muutettuun pariin yksityiskohta: quotella (tai ':llä) tehtyä listaa ei saisi muuttaa: sellainen on vakiolista Esimerkkejä (define x (list 1 2)) (define y x) (define z (cdr x)) (set-car! x 3) ; nyt x = (3 2), y = (3 2), z = (2) (set-cdr! x (list 4)) ; nyt x = (3 4), y = (3 4), z = (2) (set-car! (cdr y) 5) ; nyt x = (3 5), y = (3 5), z = (2) (define (f a) (set-car! a (+ (car a) 1)) (car x)) (f x) ; palauttaa 4; nyt x = (4 5), y = (4 5), z = (2)
Entä set!? miksi tarvitaan set-car! vielä set!:n lisäksi? set! muuttaa vain sitä, mihin tietty muuttuja osoittaa sillä siis ei voi vaikuttaa esim. muihin samaan kohtaan osoittaviin muuttujiin set-car! ja set-cdr! sen sijaan muuttavat muistissa olevia arvoja (joihin jokin muuttuja tai tietorakenne saattaa osoittaa) tausta-ajatus: jaetaan muisti kahtia: 1) muuttujasidonnat (eli nimiosoitin -parit) ja 2) varsinaiset arvot; set! asettaa vain 1-muistia ja set-car!/set-cdr! vain 2-muistia sama idea on useimmissa muissakin kielissä: esim. Pythonissa x = 3 vs. x[0] = 3 mm. Common Lisp -kielessä set!:n vastineella voi muokata myös tietorakenteita Schemessä tätä ei ole, mutta se voisi olla esimerkiksi: (set! (car (cdr l)) 32) muuttaisi l:n toista alkiota mutta oikeastaan tällainen set! on vain lyhyempi syntaksi set-car!:lle ja set-cdr!:lle: ylläoleva on tavallisessa Schemessä (set-car! (cdr l) 32)
Esimerkki: First In First Out -jono queue.scm (SICP 3.3.2) (define (empty-queue? queue) (null? (front-ptr queue))) (define (make-queue) (cons '() '())) (define (front-queue queue) (if (empty-queue? queue) (error "FRONT called with an empty queue" queue) (car (front-ptr queue)))) (define (insert-queue! queue item) (let ((new-pair (cons item '()))) (cond ((empty-queue? queue) (set-front-ptr! queue new-pair) (set-rear-ptr! queue new-pair) queue) (else (set-cdr! (rear-ptr queue) new-pair) (set-rear-ptr! queue new-pair) queue)))) (define (delete-queue! queue) (cond ((empty-queue? queue) (error "DELETE! from empty queue" queue)) (else (set-front-ptr! queue (cdr (front-ptr queue))) queue))) (define (front-ptr queue) (car queue)) ; apufunktioita (define (rear-ptr queue) (cdr queue)) (define (set-front-ptr! queue item) (set-car! queue item)) (define (set-rear-ptr! queue item) (set-cdr! queue item)) Ajoesimerkki: (define q (make-queue)) (insert-queue! q 'a) (insert-queue! q 'b) (delete-queue! q) a
Funktiorajapinta vs. oliorajapinta edellisen kalvon esimerkin rajapinta koostui funktioista: (make-queue) (empty-queue? queue) (front-queue queue) (insert-queue! queue item) (delete-queue! queue) samankaltaisen rajapinnan voisi tehdä olioilla: (make-queue) q = new Queue (q 'empty?) q.is_empty() (q 'front) q.front() (q 'insert! item) q.insert(item) (q 'delete!) q.delete() muutamia näiden eroja: syntaksi: operaatio kohde argumentit (prex) vai kohde operaatio argumentit (inx) oliorajapinnassa globaaleja nimiä on vähemmän (ja metodien nimet voivat olla lyhyempiä) jos haluaa käsitellä esim. kolmea erilaista oliota, pitää valita keinotekoisesti yksi, johon metodin laittaa; funktiorajapinnassa tämä yksi kohde ei ole erityisasemassa voiko metodia käyttää funktioargumenttina? (riippuu kielestä)
Mikä on samaa? (SICP 3.3.1) jos tietorakennetta voi muuttaa, sillä on väliä, tehdäänkö siitä kopioita vai viitataanko samaan rakenteeseen mitä tarkoittaa, että x ja y ovat samat? Schemessä: (eq? x y): sama rakenne (sama muistiosoite); (equal? x y): sama tai samanlainen omia tietorakenteita tehdessä pitää yleensä ottaa tähän kantaa, mahdollisesti määritellä useampi yhtäsuuruus omia rajapintoja tehdessä kannattaa miettiä etukäteen: 1 muuttaako rajapinnan proseduuri f argumenttina saamaansa tietorakennetta? 2 saako joku muu proseduuri muuttaa f:lle antamaansa tietorakennetta myöhemmin (jos f ottaa sen talteen)? 3 saako f:n palauttamaa tietorakennetta muuttaa? jos tätä ei mieti kunnolla tai ole kerrottu kirjastorajapinnan dokumentaatiossa, päätyy helposti tekemään ylimääräisiä kopioita varmuuden vuoksi