Ohjelmoinnin peruskurssien laaja oppimäärä Esittelyluento 2: Scheme-ohjelmointikieli ja mihin se on hyvä Riku Saikkonen 5. 10. 2010
Sisältö 1 Scheme-kielen pikaesittely 2 Kurssin käyttämästä kielestä 3 Muutama Scheme-esimerkki 4 Funktionaalinen ohjelmointi 5 Scheme-kieli käytännössä 6 Lopuksi
Scheme-kielen suunnitteluperiaate Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary. Scheme demonstrates that a very small number of rules for forming expressions, with no restrictions on how they are composed, suce to form a practical and ecient programming language that is exible enough to support most of the major programming paradigms in use today. R 5 RS (Scheme-kielen määrittely)
Scheme lyhyesti 1/2 muuttujien tyyppejä ei tarvitse kertoa: Scheme on dynaamisesti tyypitetty, kuten esim. Python ja Ruby tyypit tarkistetaan kun koodia suoritetaan tulkattavissa oleva kieli automaattinen muistinhallinta (roskankeruu) omalaatuinen prex-syntaksi: kielen rakenteet ovat muotoa ( operaattori operandi operandi...) esim. (* 2 (+ 3 4)), vrt. Javassa 2 * (3 + 4) vaatii hieman totuttelua ohjelmoijalta helpottaa mm. makrojen tekemistä ja kielen laajentamista koodi on dataa: Scheme-ohjelman käsittely toisella ohjelmalla on helppoa
Esimerkkejä syntaksista Scheme-koodinpätkiä (+ 5 (* x 2)) (/ (+ x y) 2) (define (average x y) (/ (+ x y) 2)) (if (< (average x y) 3.0) 3.0 (+ x y)) (let ((avg (average x y)) (sum (+ x y))) (if (and (>= avg 0) (< (* avg avg) sum)) (* sum sum sum) (+ avg sum)) Vastaava Java-koodi 5 + x*2 (x+y) / 2 static double average(double x, double y) { return (x+y) / 2; } if (average(x, y) < 3.0) { return 3.0; } else { return x+y; } { double avg = average(x, y); double sum = x+y; if (avg >= 0 && avg*avg < sum) { return sum * sum * sum; } else { return avg + sum; } }
Scheme lyhyesti 2/2 leksikaalinen skooppi (kuten Javassakin) paikalliset muuttujat näkyvät sen lohkon sisällä, jossa ne on määritelty ei eroa lauseilla ja lausekkeilla (melkein mitä tahansa voi laittaa minkä tahansa sisälle) jokaisella lausekkeella on (paluu)arvo puolifunktionaalinen kieli: tukee funktionaalista ohjelmointityyliä, mutta ei pakota siihen ensimmäisen luokan funktiot Schemessä funktiot ovat kuin mitä tahansa muuttujia esim. funktio voi tehdä ja palauttaa toisen funktion erikoisuuksia: laiskat arvot, makrot, kontinuaatiot näitä käsitteitä käsitellään kurssilla tarkemmin...
Silmukat ja häntärekursio (SICP 1.2) lausekielissä iteratiivista laskentaa tehdään usein silmukoilla (for, while, jne.) Schemessä tyypillisempää on tehdä apufunktio, joka kutsuu itseään häntärekursiivisesti eli niin, että funktiokutsun jälkeen ei tehdä mitään Schemessä tämä on tehokasta (esim. Javassa pino kasvaisi) Kertoma häntärekursiolla (define (factorial n) (define (iter p c) (if (> c n) p (iter (* c p) (+ c 1)))) (iter 1 1)) Javalla tai C:llä: int factorial(int n) { int p = 1, c = 1; while (c <= n) { p = c * p; c++; } return p; }
Pieni esimerkki Schemen suunnitteluperiaatteesta edeltä: ei eroa lauseilla ja lausekkeilla (melkein mitä tahansa voi laittaa minkä tahansa sisälle) Javassa ja C:ssä on kaksi iä, lause ja lauseke: if (x == 3) b = 5; else b = 8; b = (x == 3)? 5 : 8; Schemessä sama if käy molempiin: (if (= x 3) (set! b 5) (set! b 8)) (set! b (if (= x 3) 5 8)) Miksei Javassa tai C:ssä voisi toimia esim: b = ({ if (x == 3) 5; else 8; }); (gcc:ssä melkein toimiikin, sen omalla C:n laajennuksella)
Sisältö 1 Scheme-kielen pikaesittely 2 Kurssin käyttämästä kielestä 3 Muutama Scheme-esimerkki 4 Funktionaalinen ohjelmointi 5 Scheme-kieli käytännössä 6 Lopuksi
Kurssin käyttämä Schemen versio syksyn kurssilla käytetään Scheme-kielen R 5 RS-nimistä versiota ei siis uudempaa R 6 RS-versiota käytämme Scheme-toteutusta nimeltä Gambit-C http://www.iro.umontreal.ca/~gambit/ interaktiivinen tulkki ja tehokas kääntäjä hyvä debuggeri melko helppo liittää muihin ohjelmointikieliin kurssin käyttämä Scheme-toteutus löytyy Niksulasta lähiaikoina: use scheme; scheme laajan kurssin sivuille tulee ohje sen asentamisesta omalle koneelle
Miksi Scheme-kieltä käytetään kurssilla? Scheme on yksinkertainen kieli ja helppo oppia kurssi pyrkii keskittymään yleisiin periaatteisiin, jotka ovat yhteisiä lähes kaikille kielille, eikä yksittäisten kielten yksityiskohtien opetteluun tarpeeksi erilainen kuin Java, jotta saa laajemman kuvan ohjelmoinnista (esim. ei olioita) rekursiivinen ajattelutapa ja funktionaalisen ohjelmoinnin perusteet ovat hyödyllisiä muussakin ohjelmoinnissa ohjelmointikielen tulkin tekeminen ja kielen muokkaaminen on helpompaa Schemellä (erityisesti ei tarvitse keskittyä kielen syntaksin käsittelyyn) hyvä kurssikirja
Kurssikirja SICP: alkuosa Abelson, H., Sussman, G. J., Sussman, J.: Structure and Interpretation of Computer Programs, 2nd edition, MIT Press tai McGraw-Hill 1996 käyttää Schemen R 5 RS-versiota ohjelmoinnin perusoppikirja, oli pitkään käytössä MIT:n ensimmäisellä ohjelmointikurssilla esittelee myös lyhyesti tietotekniikan osa-alueita miksi ohjelmointikielet ovat sellaisia kuin ovat? kantava teema: abstraktioiden tekeminen eri tavoilla menee asioihin syvemmälle kuin useimmat muut perusoppikirjat
Kurssikirja SICP: loppuosa kurssikirjan tulkkiosio (luku 4) opettaa: pienen ohjelmointikielen tulkin tekeminen ei ole vaikeaa kokeilemaan ja miettimään mitä tapahtuisi jos kielessä olisi... ja antaa esimerkkejä harvinaisemmista ohjelmointiparadigmoista (esim. logiikkaohjelmointi) kääntäjäosio (luku 5) esittelee pienen assembler-kielen ja kääntäjän perustoiminnot sekä käsittelee muistinhallintaa kurssilla jatketaan tulkeista hieman eri suuntaan kuin kirjassa, mm. katsotaan miten oliot voi toteuttaa
Miksi tulkkeja? Schemen ja tulkkien avulla on helpompi ymmärtää muiden ohjelmointikielten toimintaa tulkki antaa konkreettisen ja tarkan kuvan siitä, miten ohjelmointikieli toimii (vaikka oikea kielen toteutus tekeekin enemmän optimointeja) Schemessä monien kielten perusominaisuudet ovat mahdollisimman siistissä muodossa yksinkertaistettuna: moni kieli = Scheme + yksityiskohtia + rajoituksia + muutama isompi lisäys kuten oliot lisäksi tulkilla voi kokeilla ohjelmointikielen muokkaamista ja oman kielen suunnittelua tulkin rakennetta voi käyttää pohjana muunlaiseen kielen käsittelyyn, esim. monimutkaisen asetustiedoston lukemiseen
Sisältö 1 Scheme-kielen pikaesittely 2 Kurssin käyttämästä kielestä 3 Muutama Scheme-esimerkki 4 Funktionaalinen ohjelmointi 5 Scheme-kieli käytännössä 6 Lopuksi
Ehtolauseita Schemessä on if:n lisäksi toinen ehtolause cond, jolle voi antaa useamman ehdon (kuten ifelse-if -ketju Javassa) Rekursiivinen kertomafunktio ifllä (define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) Sama condin avulla (define (fact n) (cond ((= n 0) 1) (else (* n (fact (- n 1))))))
Linkitetyt listat (SICP 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 on jono pareja, joka päättyy tyhjään listaan nil (tai '()): (cons 1 (cons 2 (cons 3 nil))) (1 2 3) myös: (list 1 2 3) ja '(1 2 3)
Listaoperaatioita (SICP 2.2.1, 2.2.3) (length l) alkioiden määrä (list-ref l i) tietty alkio (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... 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)
Koodi on dataa: sulkulausekkeet ja quote (SICP 2.3) sulkulauseke (S-expression, symbolic expression): Lisp-syntaksin mukainen lauseke tai siitä tehty listarakenne esim. (+ (* 3 x) y) voi tulkita joko Scheme-koodina tai listana, joka sisältää symboleita, numeroita ja alilistan symboli: Schemen tietotyyppi, joka käyttäytyy suunnilleen samoin kuin merkkijono Scheme-koodissa '(+ (* 3 x) y) tai pidemmin (quote (+ (* 3 x) y)) tuottaa listarakenteen koodi (if (= 2 1) 4 3) palauttaa kokonaisluvun 3, '(if (= 2 1) 4 3) palauttaa linkitetyn listan, jonka ensimmäinen alkio on symboli if 'x palauttaa symbolin x, pelkkä x hakee muuttujan x arvon Scheme-koodin näköisiä lausekkeita on helppo käsitellä ohjelmallisesti, koska niistä saa tavallisia listoja
Symbolinen derivoija (SICP 2.3.2) Miten tekisit ohjelman, joka ottaa aritmeettisen lausekkeen (esim. 2x + 3 eli (+ (* 2 x) 3)) ja muuttujan (x) ja palauttaa derivaatan (2)? Riittää osata summan ja tulon derivointisäännöt: D(x + y) = Dx + Dy Dxy = xy + x y Ja vakion sekä muuttujan derivointisäännöt: d D vakio = 0 x = 1 d y = 0 dx dx Eikä tarvitse sieventää Kuinka paljon työtä tällaisen ohjelman tekemisessä olisi?
Ratkaisu (kokonaan!) (SICP 2.3.2) (define (deriv exp var) (cond ((number? exp) 0) ((symbol? exp) (if (eq? exp var) 1 0)) ((and (pair? exp) (eq? (car exp) '+)) (list '+ (deriv (cadr exp) var) (deriv (caddr exp) var))) ((and (pair? exp) (eq? (car exp) '*)) (list '+ (list '* (cadr exp) (deriv (caddr exp) var)) (list '* (deriv (cadr exp) var) (caddr exp)))) (else (error "unknown expression type -- DERIV" exp)))) Ajoesimerkkejä: (deriv 15 'x) 0 (deriv 'x 'x) 1 (deriv 'y 'x) 0 (deriv '(+ x 3) 'x) (+ 1 0) (deriv '(* x y) 'x) (+ (* x 0) (* 1 y)) (deriv '((+ (* 2 x) 3) 'x) (+ (+ (* 2 1) (* 0 x)) 0)
Sisältö 1 Scheme-kielen pikaesittely 2 Kurssin käyttämästä kielestä 3 Muutama Scheme-esimerkki 4 Funktionaalinen ohjelmointi 5 Scheme-kieli käytännössä 6 Lopuksi
Ohjelmointiparadigmat yleisimmät ohjelmointiparadigmat: imperatiivinen eli lausekielinen ohjelmointi (imperatiivinen) olio-ohjelmointi funktionaalinen ohjelmointi logiikkaohjelmointi määritelmät eivät ole kovin tarkkarajaisia, ja muitakin paradigmoja on jotkin ohjelmointikielet tukevat lähinnä yhtä paradigmoista, esim. C, Java, Haskell, Prolog toiset (uudemmat?) tukevat useampaa, esim. Python, Ruby, Scala, JavaScript paradigmoja käytetään osin myös kielissä, jotka eivät tue niitä kunnolla (esim. olio-ohjelmointia C:ssä)
Miksi eri paradigmoja kannattaa opetella? erilaisten paradigmojen tunteminen antaa enemmän vaihtoehtoja ohjelman suunnitteluun (vaikkei käyttäisikään erikoisempia kieliä) jokainen paradigma on paras tunnettu työkalu johonkin asiaan uuden kielen opettelu on helpompaa, jos on nähnyt paljon erilaisia kieliä varsinkin jos paradigma on tuttu eri paradigmojen kieletkin lainaavat usein ominaisuuksia toisiltaan auttaa ymmärtämään myös abstraktioita esim. Javan String-luokka on funktionaalinen: merkkijonoja ei voi muuttaa (eikä niistä siksi tarvitse tehdä kopioita)
Funktionaalinen ohjelmointi funktionaalinen ohjelmointi tarjoaa uusia tapoja jakaa ohjelma osiin abstraktioita, joilla ohjelman voi koota pienistä helposti ymmärrettävistä paloista usein pyritään siihen, että lopullinen koodi kertoo, mitä pitää tehdä eikä miten se tehdään (eli deklaratiiviseen ohjelmointiin), ja yksityiskohdat piilotetaan abstraktioihin perustuvat usein funktioiden käyttöön argumentteina ja paluuarvoina toisaalta funktionaalisuus on erilainen tapa ajatella ohjelman tilaa imperatiivisessa ohjelmoinnissa ohjelman käskyt yleensä muuttavat ohjelman senhetkistä tilaa funktionaalisessa useammin luodaan uusi versio tilasta, jolloin sekä vanhaa että uutta voi käsitellä
Esimerkki funktioiden käytöstä (SICP 1.31.3.2) Halutaan arvioida π:n arvoa kaavalla π 8 = 1 1 3 + 1 5 7 + 1 9 11 +. Ratkaisu oman summa-abstraktion avulla { b 0, jos a > b f (n) = f (a) + b n=next(a) f (n) muuten. n=a (define (sum f a next b) (if (> a b) 0 (+ (f a) (sum f (next a) next b)))) (define (pi-f x) (/ 1.0 (* x (+ x 2)))) (define (pi-next x) (+ x 4)) (define (pi-sum a b) (sum pi-f a pi-next b)) Testiajo: (* 8 (pi-sum 1 1000)) 3.139592655589783
Muita funktionaalisia ohjelmointikieliä Schemen lisäksi yleisiä funktionaalisia ohjelmointikieliä ovat mm. Haskell, Ocaml, Standard ML ja Erlang funktionaalinen ohjelmointitapa on viety näissä pidemmälle kuin Schemessä Schemen osaaminen helpottaa näiden kielten opettelua, vaikka kielet sinänsä eivät ole kovin lähellä Schemeä monet uudemmat ei-funktionaaliset kielet tukevat funktionaalista ohjelmointia jonkin verran: esim. Ruby, JavaScript, Scala, (Python)
Sisältö 1 Scheme-kielen pikaesittely 2 Kurssin käyttämästä kielestä 3 Muutama Scheme-esimerkki 4 Funktionaalinen ohjelmointi 5 Scheme-kieli käytännössä 6 Lopuksi
Mihin Schemeä käytetään oikeasti? laajennus- tai skriptikielenä muulla kielellä kirjoitettuun ohjelmaan esim. Gimp-kuvankäsittelyohjelmaa voi laajentaa Schemellä pohjana omalle pienelle (tai isolle) ohjelmointikielelle, koska Scheme on siisti ja helposti laajennettava kieli esim. GNU R -tilasto-ohjelman ohjauskieli perustuu Schemeen esim. JavaScript on ottanut mallia Schemestä ohjelmointikielitutkimuksessa uusien ideoiden kokeilussa opetuskäytössä ja joskus myös Scheme-ohjelmiin (mm. SSH on tehnyt tuotteita Schemellä)
Gimp-esimerkkejä Gimp-kuvankäsittelyohjelmaa voi laajentaa Schemellä, Perlillä tai Pythonilla (ja C:llä) Scheme oli ensimmäinen näistä, ja edelleen monet laajennuksista tehdään Schemellä muutama yksinkertainen esimerkki: Pencil drawing: http://registry.gimp.org/node/22018 Blue Sky & Clouds: http://registry.gimp.org/node/193 laajennusten teko-ohje: http://www.gimp.org/docs/ scheme_plugin/scheme-sample.html Gimpin käyttämässä Scheme-toteutuksessa on muutama erikoisuus tavalliseen Schemeen verrattuna
Muita Lispin murteita Scheme kuuluu Lisp-nimiseen kieliperheeseen Common Lisp on paljon laajempi Lisp-kieli, jota on käytetty enemmän käytännössä esim. symbolinen matematiikkaohjelmisto Maxima on tehty Common Lispillä Emacs Lispillä on tehty paljon lisätoimintoja Emacs-tekstieditoriin (editoriominaisuuksien lisäksi esim. sähköposti- ja IRC-ohjelmat)
Sisältö 1 Scheme-kielen pikaesittely 2 Kurssin käyttämästä kielestä 3 Muutama Scheme-esimerkki 4 Funktionaalinen ohjelmointi 5 Scheme-kieli käytännössä 6 Lopuksi
Lopuksi tässä luennossa näkyi melko pieni osa Schemestä, ja vain muutama esimerkki Scheme-kieltä ehtii opetella rauhassa kurssin aikana samoin edellä olevia käsitteitä ja kirjan esimerkkejä käsitellään tarkemmin ja rauhallisemmin kurssilla myös funktionaalisesta ohjelmoinnista puhutaan myöhemmin lisää keväällä siirrytään opettelemaan Scalaa, jossa on piirteitä mm. Javasta ja Schemestä
Seuraavaksi laajalla kurssilla Laajassa oppimäärässä tulossa: 19.10. Java-tehtävät tehtynä viimeistään 20.10. hakemuksen viimeinen jättöpäivä 27.10. tai aiemmin: tieto laajalle kurssille valituista opiskelijoista lähiviikkoina tulossa kotisivuille: kolmen harjoitustehtäväkierroksen deadlinet ensimmäisen kierroksen tehtävät (automaattinen tarkistusjärjestelmä vasta opiskelijavalintojen jälkeen) kurssin Scheme-tulkin asennusohjeet alustava luettelo kurssikirjasta käsiteltävistä kohdista ja muusta oppimateriaalista