Ohjelmoinnin peruskurssien laaja oppimäärä Luento 9: Miten oliot toteutetaan, skriptausta Riku Saikkonen (osa kalvoista on suoraan ei-laajan kurssin luennoista) 18. 3. 2013
Sisältö 1 Oliot Scheme-tulkkiin 2
Tee se itse: oliokieli lisätään SICP-kirjan metasirkulaariseen Scheme-tulkkiin yksinkertainen oliojärjestelmä tavoitteen saada selville: millaista uutta syntaksia olioihin tarvitaan? kuinka paljon koodia olioiden toteuttaminen vaatii? miten oliot käytännössä toimivat (esim. miten metodikutsu toteutetaan)? järjestelmä tukee: luokkia: kenttiä ja metodeita perintää ja dynaamista metodinvalintaa (ei moniperintää) oliot rakennetaan Schemen päälle eli olioita voi käyttää sekaisin muun koodin kanssa kaikkea ei tehdä niin hienosti kuin voisi (mm. ei proseduurien sisäisiä luokkia) kalvoissa vain osa toteutuksesta (mm. syntaksin ja tietorakenteiden käsittelyssä on melko paljon yksinkertaisia yksityiskohtia)
Oliojärjestelmämme syntaksi Koodiesimerkki tässä tulkattavasta koodista (define-class point object (define-field x 0) (define-field y 0) (define-method (dist point) (define (square x) (* x x)) (sqrt (+ (square (- (this get-x) (point get-x))) (square (- (this get-x) (point get-x)))))) (define-method (print) (display "Point at ") (display (this get-x)) (display ", ") (display (this get-y)) (newline))) point.scm (define-class colored-point point (define-field color 'black) (define-method (print) (display "Colored point at ") (display (this get-x)) (display ", ") (display (this get-y)) (display " colored ") (display (this get-color)) (newline))) (define p1 (new point 5 8)) (p1 set-x! 5) (p1 set-y! 8) (define p2 (new colored-point 2 3 "blue")) (p2 set-x! 2) (p2 set-y! 3) (p2 set-color! "blue") (p1 print) (p2 print) (p1 dist p2) (p2 dist p1)
Kertaus: Mitä oliosta on tallessa muistissa? ajon aikana luokat ja metodit ovat jo tiedossa: ne on luotu käännösaikana tai luokkia määritellessä sen sijaan kenttien arvot ovat jokaiselle oliolle yksilöllisiä ajon aikana jokaisesta oliosta on tallessa: kenttien arvot viittaus luokan tiedot (mm. metodit) sisältävään tietorakenteeseen mikä sitten on metodi? proseduuri, joka saa implisiittisenä argumenttina olion, jonka kautta metodia kutsuttiin (Javassa tästä tulee this; Pythonin self näkyy eksplisiittisesti metodin määritelmässä, muttei näy kutsussa) metodista on siis (esim. Javassa) muistissa vain koodi vrt. proseduuri Scheme-tulkissa: koodi ja määrittely-ympäristö (sisäkkäisten luokkien tukemiseen voisi tarvita myös määrittely-ympäristöä)
Olioiden toteutus 1/6 Muutokset tulkkiin: tulkin tietorakenteita (define (make-method body parameters environment) (list 'method body parameters environment)) (define (method-body method) (cadr method)) ; jne. m-eval-object (define (make-class name parent method-alist field-names field-value-exps) (list 'class name parent method-alist field-names field-value-exps)) (define (class-name class) (cadr class)) ; jne. (define (make-object class field-values) (cons 'object (cons class field-values))) (define (object? val) (tagged-list? val 'object)) (define (object-class object) (cadr object)) (define (object-field-value-get object field-index) (list-ref (cddr object) field-index)) (define (object-field-value-set! object field-index value) (set-car! (list-tail (cddr object) field-index) value)) metodissa on koodi, argumentit ja (globaali) ympäristö luokassa on isäluokka, metodit, kentät, kenttien alkuarvolausekkeet oliossa on tallessa viittaus luokkaan ja kenttien arvot
Olioiden toteutus 2/6 Muutokset tulkkiin: globaali luokkalista m-eval-object (define (make-initial-class-list) (list (list 'object (make-class 'object #f '() '() '())))) (define the-class-list (make-initial-class-list)) (define (find-class class-name) (let ((result (assq class-name the-class-list))) (if result (cadr result) (error "Unknown class name -- FIND-CLASS" class-name)))) (define (add-to-class-list! class-name class) (set! the-class-list (cons (list class-name class) the-class-list))) pidetään luokkien tietoja globaalissa listassa, josta find-class hakee luokan nimellä tämän vuoksi emme tue sisäkkäisiä luokkamäärittelyjä (ja metodiin talletettu ympäristö on aina globaali ympäristö, jos define-classia esiintyy vain tulkin päätasolla) globaalin listan sijaan luokat voisi periaatteessa myös yhdistää jollain tavalla ympäristöihin
Olioiden toteutus 3/6 Muutokset tulkkiin: uusi eval m-eval-object (define (eval exp env) (cond... ((define-class? exp) (eval-class-definition exp env)) ((new? exp) (eval-new (new-class-name exp) (list-of-values (new-arguments exp) env) env)) ((application? exp) (let ((evaled-op (eval (car exp) env))) (if (object? evaled-op) ; onko tämä metodikutsu? (apply-method evaled-op (cadr exp) ; metodin nimi (list-of-values (cddr exp) env)) (apply evaled-op (list-of-values (cdr exp) env)))))... )) evaliin tulee kolme uutta haaraa: 1 luokan määritteleminen (define-classin sisältä käsitellään samalla kenttiä ja metodeja määrittelevät lausekeet) 2 uuden olion tekeminen eli new 3 metodin kutsuminen (erilainen kuin proseduurikutsu)
Olioiden toteutus 4/6 Muutokset tulkkiin: uuden luokan luonti (osin) m-eval-object (define (eval-class-definition exp env) (let* ((new-class-name (class-definition-name exp)) (parent (find-class (class-definition-parent-name exp))) (field-names (map car (class-definition-fields exp))) (field-value-exps (map cadr (class-definition-fields exp))) (all-field-names (append (class-field-names parent) field-names)) (all-field-value-exps (append (class-field-value-exps parent) field-value-exps)) (user-methods (class-definition-make-method-alist exp env)) (get-set-methods (make-get-set-methods (length (class-field-names parent)) field-names env)) (the-new-class (make-class new-class-name parent (append user-methods get-set-methods) all-field-names all-field-value-exps))) (add-to-class-list! new-class-name the-new-class))) tämä lähinnä hakee define-class-lausekkeesta isäluokan, kentät ja metodit ja antaa ne lopulta make-class:lle make-get-set-methods tekee kentille get- ja set-metodit
Olioiden toteutus 5/6 Muutokset tulkkiin: uuden olion tekeminen m-eval-object (define (eval-new class-name constructor-arguments env) (let* ((class (find-class class-name)) (field-values (list-of-values (class-field-value-exps class) env)) (obj (make-object class field-values))) ;; käyttäjän määrittelemää konstruktoria pitäisi kutsua tässä ;; (ei vielä toteutettu) obj)) yksittäisessä oliossa on tallessa tieto sen luokasta (eli viittaus class-tietorakenteeseen) sekä kaikkien kenttien arvot yllä list-of-values suorittaa define-field:llä määritellyt lausekkeet, joista tulevat kenttien oletusarvot (toinen tapa olisi suorittaa ne vain kerran luokkaa määritellessä)
Olioiden toteutus 6/6 Muutokset tulkkiin: metodikutsu m-eval-object (define (apply-method object method-name arguments) (let ((method (find-method object method-name))) (eval-sequence (method-body method) (extend-environment (cons 'this (method-parameters method)) (cons object arguments) (method-environment method))))) (define (find-method object method-name) (define (find-in-class class) (assq method-name (class-method-alist class))) (define (find-in-parents class) (and class (or (find-in-class class) (find-in-parents (class-parent class))))) (let ((result (find-in-parents (object-class object)))) (if result (cadr result) (error "Unknown method name -- FIND-METHOD" method-name)))) kuten proseduurin kutsu, mutta ylimääräinen argumentti this metodia haetaan ensin olion luokasta ja sitten sen isistä
Mihin pääsimme? yksinkertaisten olioiden toteuttaminen oli melko helppoa enimmäkseen uuden syntaksin ja luokkatietorakenteen käsittelyä tosin koodia tuli yhteensä aika paljon järjestelmämme muistuttaa Javan ja Pythonin oliojärjestelmiä yksinkertaistettuna jotta se järjestelmä olisi kokonainen, siihen pitäisi lisätä ainakin omat konstruktorit ja super (harjoitustehtävinä) muuta mahdollista lisättävää: moniperintä (tässä voisi kokeilla eri toteutusvaihtoehtoja) abstraktit luokat luokkamuuttujat reektio (tapa nähdä kielen sisältä esim. mitä metodeita oliolla on) instanceof/isinstance, final yms. yksittäisiä ominaisuuksia Java-tyyliset näkyvyydet (vähän työläämpää)
Mitä voisi tehdä toisin? 1/2 automaattisia get- ja set-metodeja ei välttämättä tarvitsisi tehdä (Javassa ja C++:ssa ei ole) mutta silloin tarvittaisiin syntaksi kenttien käsittelyyn nyt niihin pääsee käsiksi vain näillä metodeilla metodien (ja proseduurien) argumenteilla voisi olla staattiset tyypit, ainakin silloin kun ne ovat olioita nyt olioargumentiksi kelpaa mikä tahansa olio, jolla on oikean nimiset metodit (ei siis välttämättä tietyn olion aliluokka) tätä tapaa kutsutaan duck typing:ksi, ja se on mm. Pythonissa, Rubyssä ja osin Common Lispissä vaihtoehto on ns. nimipohjainen tyyppijärjestelmä (nominal type system), kuten esim. Javassa ja C++:ssa tässä vaihtoehtoisessa tavassa voisi tukea myös useampaa samannimistä metodia, joilla on eri argumentit esimerkki: aiemmassa esimerkkikoodissa Point-olioksi kelpaa joko a) mikä tahansa olio jolla on mm. pprint-niminen metodi, tai b) vain Point-luokan aliluokan olio
Mitä voisi tehdä toisin? 2/2 metodien ei tarvitsisi kuulua yksittäisiin luokkiin: metodi on kuten proseduuri, jonka ensimmäinen argumentti (this/self) on erityisasemassa (ja jota kutsutaan olion kautta) mutta metodien sijaan (tai lisäksi) voisi olla vain funktioita, joiden kaikki argumentit ovat samanarvoisia C++ kutsuu tällaisia friend-funktioiksi, Common Lisp geneerisiksi funktioiksi periaatteessa luokka-käsitettä ei tarvittaisi, jos olioista voisi tehdä muutettuja kopioita esim. Javascriptissä luokan sijaan tehdään prototyyppiolio, josta varsinaiset oliot kopioidaan olioiden syntaksikin voisi tietysti olla eri...
Sisältö 1 Oliot Scheme-tulkkiin 2
Mikä on skripti? skriptien tyypillisiä piirteitä: lyhyt ohjelma (jopa vain 1 rivi, harvoin yli 500) nopeasti tehty (minuuteista tunteihin) usein vain tekijän itsensä käyttöön tarkoitettu joskus kertakäyttöinen tai vain muutaman kerran käytettävä skripteillä tyypillisimmillään: käsitellään tekstimuotoista dataa esim. muodosta toiseen käytetään muita ohjelmia apuna: skripti käynnistää usein aliohjelmia (harvemmin käyttää vastaavia kirjastoja) tehdään yksittäinen korjaus tiettyyn tilanteeseen esimerkkejä: laajan tehtävistä: MP3-tehtävä, karttatehtävä, kuvaajatehtävä korjataan tiedostojen nimiä tai oikeuksia luetaan tekstitiedosto(i)sta tiettyjä kohtia ja tehdään niistä tiedoista esim. koostetaulukko käynnistetään jokin ohjelma ja alustetaan tilanne sopivaksi sitä varten (esim. /usr/bin/firefox on usein skripti)
Unix shell-skriptit (sh) shell-skriptit ovat Unixin tavallisen komentotulkin kielellä tehtyjä pieniä ohjelmia komentotulkkikieliä on useita, mutta skriptit tehdään lähinnä ns. Bourne/POSIX sh:lla (josta esim. bash on laajennus) käytetään eniten (Unix-)ylläpidossa ja mm. Linux-levitysten käynnistysskripteissä hyvää: sama kieli kuin interaktiivisessa komentotulkissa; helppo ajaa ja yhdistellä komentoja (eli muita ohjelmia); valmiina kaikissa Unixeissa puutteita: ei tietorakenteita; syntaksi menee vaikeaksi monimutkaisissa asioissa (aliohjelmien vuoksi useita syntakseja sisäkkäin); turvattomien merkkijonojen käsittelyssä pitää olla tarkkana; monta toteutusta, vain tietyt ominaisuudet toimivat kaikissa; toimii parhaiten Unixissa esimerkkejä: /usr/bin/firefox, /etc/init.d/x11-common ohje esim. http://tldp.org/ldp/abs/html/
Perl Perl-skriptikielen syntaksi muistuttaa mm. shell-skriptejä ja C:tä yhtenäisempi kieli kuin shell-skriptit (voi tehdä paljon enemmän kielen sisällä ilman apuohjelmia) paljon valmiita kirjastoja (kuten Pythonissakin) monipuolinen myös ohjelmointikielenä (esim. moduulit, oliot, ensimmäisen luokan funktiot, leksikaalinen ja dynaaminen sidonta, jne.), joskin välillä erikoinen erityisen hyvä säännöllisten lausekkeiden käsittelyssä (ja siis skripteissä, jotka käyttävät niitä paljon) puutteita esim: sisäkkäiset tietorakenteet hankalia käyttää, skriptikielenä ei tarkoitettu isoihin ohjelmiin usein shell-skripteistä siirrytään Perliin tai Pythoniin, kun komentotulkki ei enää riitä vanha skriptikieli AWK on vähän Perlin kaltainen, mutta yksinkertaisempi (käytetään yhdessä shell-skriptien kanssa) esimerkkejä: /usr/bin/shasum, gnome-terminal.wrapper ohje: esim. manuaalisivu man perlintro
Skriptaus Pythonilla Python toimii myös skriptikielenä ohjelman ja skriptin välillä ei ole tarkkaa rajaa Python ja Perl ovat skriptikielinä melko samankaltaisia eroja: Pythonissa on mm. parempi tuki tietorakenteille, Perlissä tiivimpää syntaksia mm. säännöllisiin lausekkeisiin ja aliohjelmien käynnistämiseen skriptaukseen sopivia kirjastoja löytyy molemmille paljon Python lienee parhaimmillaan skripteissä, jotka käsittelevät (suurta määrää, varsinkin sisäkkäistä) dataa jos skriptin pitää vain käynnistää muutama komento, shell-skripti tai Perl ovat yleisempiä ratkaisuja (mutta Pythonkin toimii) Python-skriptiä on ehkä muita kahta helpompi laajentaa oikeammaksi ohjelmaksi esim. lisätä graanen käyttöliittymä (tehdään joskus Perlissäkin) Python-skripti tuntuu pysyvän todennäköisemmin helppolukuisena hyvin pienissä skripteissä Python vaatinee vähän enemmän koodia sillä lyhennysmerkintöjä on vähemmän ja esim. yksi rivi ei riitä
Esimerkki Python-skriptistä: koneen yleisimmät prosessit Osa ps -ef -komennon tulostetta UID PID PPID C STIME TTY TIME CMD root 12502 576 0 Aug 29? 0:22 screen irssi root 17117 1612 0 Jul 07? 0:00 /usr/lib/ssh/sshd root 12077 1612 0 16:57:57? 0:00 /usr/lib/ssh/sshd Python-skripti import sys cmds = dict() hdr = sys.stdin.readline() ind = hdr.rfind(" CMD")+1 for l in sys.stdin: cmd = l[ind:].partition(" ")[0].strip() if cmd in cmds: cmds[cmd] += 1 else: cmds[cmd] = 1 Ajoesimerkki 500x ssh-agent 418x /bin/tcsh 416x screen 308x irssi 307x /usr/lib/ssh/ssh 123x -tcsh 96x ssh 65x /c/bin/zsh 40x pine 17x /c/bin/bash scmds = sorted(cmds.items(), key=lambda x: x[1], reverse=true) for c,v in scmds[:10]: print str(v)+"x "+c