Ohjelmoinnin peruskurssien laaja oppimäärä Esittelyluento 1: Ohjelmointirajapintoja todellisista ohjelmista Riku Saikkonen 28. 9. 2010
Sisältö 1 Laaja oppimäärä lyhyesti 2 Miten piirrän ohjelmassani kuvaajan datasta? 3 Miten luetaan ja kirjoitetaan dataa? 4 Tekstimuotoiset asetustiedostot kielinä 5 Rajapinnoista
Mitä laajassa oppimäärässä on? vaihtoehtoinen suoritustapa ohjelmoinnin peruskursseille käydään läpi enemmän asioita ja mennään usein peruskursseja syvemmälle kokeillaan ohjelmointia isossa ryhmässä syksyllä: peruskurssin tehtävät + tehtäviä Scheme-kielellä keväällä: tehtäviä + ohjelmointiprojekti, enimmäkseen Scala-kielellä tarkemmin kotisivuilla: https://wiki.aalto.fi/display/laajaohj
Syksyn kurssin sisältö kurssiesitteestä: T-106.1200-peruskurssin sisältö Ohjelmointirajapintojen suunnittelu ja erilaiset abstraktiot Rekursio ongelmanratkaisumenetelmänä Funktionaalisen ohjelmoinnin perusteita Ohjelmointikielten tulkkien toteuttaminen Scheme-ohjelmointikieli käytännössä käydään pienten tehtävien avulla läpi noin 150 sivua kurssikirjasta ja hiukan muuta asiaa
Kevään kurssien sisältöä keväällä tehdään mm. seuraavaa: jatketaan syksyn kurssin asioita vähän opetellaan Scala-ohjelmointikieltä opetellaan tietorakenteita ja algoritmeja katsellaan hieman oikeita kirjastoja ja ohjelmia muokataan annettua Scala-koodipohjaa isossa ryhmässä ja harjoitellaan realistisia ryhmätyötapoja mutta muodostamme kevään sisältöä tarkemmin syksyn kurssin aikana...
Loppuluennosta seuraavaksi käydään läpi esimerkinomaisesti muutamia rajapintoja oikeista ohjelmista tarkoitus on: herättää ajatuksia siitä, millaisia asioita rajapintojen suunnittelussa mietitään (tätä käsitellään kurssilla lisää ja paljon rauhallisemmin) saada ideoita siitä, miten tässä käsiteltyjä esimerkkejä voisi käyttää omassa koodissaan ehkä ymmärtää, miksi rajapinnat ja abstraktiot mainittiin edellä ei siis kannata opetella näitä esimerkkejä, eikä huolestua jos ei ymmärrä kaikkea tämä luento on melko erilainen kuin suuri osa laajasta oppimäärästä tulee olemaan; seuraava luento esittelee laajaa konkreettisemmin
Sisältö 1 Laaja oppimäärä lyhyesti 2 Miten piirrän ohjelmassani kuvaajan datasta? 3 Miten luetaan ja kirjoitetaan dataa? 4 Tekstimuotoiset asetustiedostot kielinä 5 Rajapinnoista
Rajapinnoista Ohjelmointirajapintoja voi tutkia eri näkökulmista: Miten käytän olemassaolevaa rajapintaa omasta ohjelmastani? (Esim. Miten käytän tietokantoja Javasta?) Miten tekisin oman rajapinnan samaan tai saman tapaiseen tehtävään? Voiko tästä rajapinnasta oppia jotain? Sopiiko tämä rajapinta (tai jokin useista olemassaolevista) juuri omaan ohjelmaani? Kannattaisiko jokin asia tehdä muulla tavalla kuin rajapinnan kautta? Onko rajapinta yleisesti ottaen hyvin suunniteltu? Suosittelisinko sitä kaverilleni? Katsotaan muutamaa hyvin erilaista rajapintaa samaan tehtävään: ohjelma tuottaa jotain dataa (esim. taulukko numeroita), ja siitä pitäisi piirtää kuvaaja.
Tavallinen kirjasto: JFreeChart Javalle tehty kuvaajanpiirtokirjasto (paljon muitakin samankaltaisia on sekä Javalle että muille kielille) luokkia datan talletukseen, eri tyyppisten kuvaajien tekemiseen, kuvaajan osien muokkaamiseen,... JFreeChart-koodiesimerkki XYSeries s = new XYSeries("data"); s.add(1.0, 2.8); s.add(2.0, 3.7);... XYDataset d = new XYSeriesCollection(s); JFreeChart c = ChartFactory.createXYLineChart("otsikko", "Aika (s)", "Matka (m)", d, PlotOrientation.VERTICAL, true, true, false); try { ChartUtilities.saveChartAsPNG(new File("kuva.png"), c, 700, 500); } catch (IOException e) {... }
Gnuplot-komentojen kirjoittaminen Gnuplot on komentoriviohjelma, jolle lähetetään tekstinä komentoja, joiden perusteella se piirtää kuvaajan komentoja voi kirjoittaa käsin, mutta toki voi myös tehdä ohjelman, joka tuottaa Gnuplot-komentoja ja käsittelee Gnuplotin tuottamaa kuvaa samalla tavalla voisi käyttää esim. Matlabia tai muita komentoriviohjelmia Esimerkki Gnuplot-komennoista set xlabel "Aika (s)" set ylabel "Matka (m)" plot '-' with linespoints 1.0 2.8 2.0 3.7. e
Pythonin Matplotlib Python-ohjelmointikielelle tehty kirjasto tarkoitettu sekä ohjelmien käyttöön että interaktiiviseen käyttöön (Gnuplot- tai Matlab-tyyliin, mutta komennot ovat Python-koodia eikä vain tähän tarkoitukseen tehtyä komentokieltä) oliopohjainen kirjasto, jonka käytöstä on pyritty tekemään mahdollisimman yksinkertaista Esimerkki Matplotlibin käytöstä (eräs käyttötapa) import matplotlib.pyplot as plt x = [1.0, 2.0,...]; y = [2.8, 3.7,...] plt.plot(x, y, marker='+', linestyle='-') plt.xlabel('aika (s)') plt.ylabel('matka (m)') plt.show()
Analysointia edellä oli kolme tapaa: JFreeChart, Gnuplot, Matplotlib neljäs tapa olisi toki piirtää kuvaaja itse ilman näitä aputyökaluja mitä sitten pitäisi käyttää? vaikea kysymys; riippuu siitä mitä ohjelma muuten tekee kaikki vaihtoehdot ovat omalla tavallaan hyviä entä voisiko näistä tavoista oppia jotain? JFreeChart lienee ohjelmoijalle tutumpi tapa kuin muut Gnuplotissa on tehty oma komentokieli ja sille tulkki; Matplotlibissä tältä on vältytty rakentamalla kuvaajanpiirtokomennot Python-kielen sisään yksityiskohtia vertailemalla oppisi lisää kuvaajanpiirtokirjaston suunnittelemisesta
Sisältö 1 Laaja oppimäärä lyhyesti 2 Miten piirrän ohjelmassani kuvaajan datasta? 3 Miten luetaan ja kirjoitetaan dataa? 4 Tekstimuotoiset asetustiedostot kielinä 5 Rajapinnoista
Rajapintoja datan liikuttamiseen mietitään seuraavaksi sitä, millä eri tavoilla ohjelman osa voi saada tai lähettää tietoa muille ohjelman osille tai muille ohjelmille: käydään samalla läpi muutama tapa lukea ja kirjoittaa dataa tiedostoihin tai esim. verkkoon oletetaan aluksi, että ohjelma ei välitä datan rakenteesta (data on esim. vain merkkijono) Esimerkkiongelma, jota voi miettiä rajapintoja käsiteltäessä: Ohjelman osa saa GPS-laitteelta paikkatietoa melko säännöllisin väliajoin; miten sitä kannattaisi lähettää eteenpäin muulle ohjelmalle?
Javan streamit 1/2 streamit (tietovirrat) ovat yksinkertainen tapa lukea ja kirjoittaa dataa stream-oliota tehdessä päätetään mistä luetaan tai minne kirjoitetaan: s = new FileWriter("tiedosto.txt"); yleensä samalla lisätään virtaan puskurimuistia: b = new BufferedWriter(s); kirjoittaa voi useammalla tavalla, esim. b.write("foo"); lopuksi virta (ja tiedosto) suljetaan: b.close(); lukeminen toimii samaan tapaan kuin kirjoittaminen edellämainitut on tarkoitettu tekstimuotoiselle datalle; ei-tekstille on samankaltaiset FileOutputStream, BufferedOutputStream jne.
Javan streamit 2/2 joskus pitää odottaa ja jatkaa vasta, kun puskuri on tyhjennetty: tähän puskuroidussa virrassa on flush()-metodi tai kysyä onko dataa saatavilla heti luettavaksi: ready() streameihin voi lisätä uusia toimintoja yhdistelemällä niitä; esim. puskurointi tai tiedon pakkaus tiedoston sijaan voi kirjoittaa esim. verkkoyhteyteen tai merkkijonoon (StringWriter) Javassa voi kirjoittaa myös olioita; tällöin Java muuttaa niiden sisällön tekstiksi tai binääridataksi (eli serialisoi) useimmissa muissakin ohjelmointikielissä on tämänkaltaiset puskuroidut streamit
Matalamman abstraktiotason read/write streamit on yleensä toteutettu kutsumalla yksinkertaisempia funktioita Unixin kirjastossa on ns. systeemikutsut: read(tiedosto, osoite, m) lukee enintään m tavua, kirjoittaa tuloksen osoitteeseen ja palauttaa lukemiensa tavujen määrän write(tiedosto, osoite, m) kirjoittaa enintään m tavua osoitteesta alkaen ja palauttaa kirjoittamiensa tavujen määrän näitä pitää kutsua silmukassa, jos haluaa lukea tai kirjoittaa niin paljon kuin mahdollista hyvä puoli: ohjelma voi reagoida siihen, että esim. vain osa halutusta datasta saatiin kirjoitettua (esimerkiksi kertoa käyttäjälle kirjoittamisen edistymisestä) yleensä stream-rajapinta on kuitenkin kätevämpi
Odottaminen ja virheet jos lukeminen tai kirjoittaminen kestää pitkään, pitäisikö sitä jäädä odottamaan? yleensä rajapinnoissa voi kertoa haluaako odottaa (blocking read/write) vai lukea/kirjoittaa vain sen verran kun voidaan tehdä heti (non-blocking) entä jos tulee virhe? rajapinta voi lähettää ohjelmalle poikkeuksen (esim. Javassa), jolla ohjelman suoritus hyppää toiseen paikkaan tai luku/kirjoitusfunktio voi palauttaa tietyn arvon virhetilanteessa (esim. luettiin -1 tavua) joskus virheestä saa tarkempia tietoja erikseen (Javassa poikkeuksen tyyppi, esim. C:ssä errno-muuttuja)
Useammasta paikasta lukeminen entä jos ohjelma haluaa lukea monesta paikasta sitä mukaa kun niistä tulee dataa? esim. useammasta auki olevasta verkkoyhteydestä kaksi päätapaa: jaetaan ohjelma useaan säikeeseen niin että jokainen lukee vain yhtä datalähdettä tai rajapinnassa on funktio, joka tutkii useampaa datalähdettä ja kertoo, missä niistä on nyt saatavilla dataa (tämän nimi on usein select tai poll) tätä pitää kutsua silmukassa, jossa tehdään jotain kaikelle saatavilla olevalle datalle
Tapahtumiin perustuva lukeminen monesta paikasta lukemisen ongelmaan on myös toisenlainen ratkaisu: lukemisen voi kääntää toisin päin ei tehdäkään niin että ohjelma lukee halutessaan dataa jonkinlaisella read-funktiolla vaan että ohjelma rekisteröi rajapinnalle callback-funktion (takaisinkutsun), jota rajapinta kutsuu aina kun lisää dataa on saatavilla siis normaalisti ohjelma vetää rajapinnalta lisää dataa silloin, kun ohjelmalle sopii; mutta tässä menetelmässä rajapinta työntää ohjelmalle dataa silloin, kun sitä on saatavilla tällaisen rajapinnan huono puoli on, että datan käsittely tapahtuu eri paikassa kuin lukemisen käynnistäminen, joten koodin etenemistä on vaikeampi seurata
Esimerkki tapahtumiin perustuvasta lukemisesta wget-ohjelman tiedonsiirron etenemistä kuvaava palkki (progress bar) on olio, jonka update-metodia muu ohjelma kutsuu aina kun siirto etenee metodi saa kaksi argumenttia: howmuch = montako tavua edellisen kutsun jälkeen on saatu, ja dltime = kauanko siirto on tähän mennessä kestänyt muu ohjelma siis työntää tälle oliolle dataa (tavumääriä), ja olio päivittää palkkia aina kun tiedonsiirto on edennyt riittävästi toinen vaihtoehto olisi, että palkin piirtämistä varten olisi silmukka, joka kysyy muulta ohjelmalta säännöllisesti, kuinka paljon dataa on tullut, ja piirtää palkin tämän mukaan
Toinen esimerkki lukemisesta ja kirjoittamisesta wget-ohjelmassa on suoraviivainen rajapinta lokitiedoston kirjoittamiseen: esim. logputs-funktiolle annetaan merkkijono ja viestin tärkeys (numero 14) ohjelman asetusten perusteella funktio joko ei tee mitään (tärkeys liian pieni) tai kirjoittaa merkkijonon joko näytölle tai lokitiedostoon lisäksi logflush pakottaa lokin puskurista eteenpäin
Kolmas esimerkki SDL-kirjastolla (jota monet reaaliaikaiset pelit käyttävät) tuotetaan ääniä näin: SDL_OpenAudio-funktiolle annetaan äänen parametrien (esim. mono vai stereo) lisäksi callback-funktio kirjasto soittaa äänidataa puskurista, ja kutsuu annettua callback-funktiota aina kun tässä puskurissa on tilaa callback-funktion tehtävänä on täyttää puskuri äänidatalla, jonka kirjasto tulee kohta soittamaan siis ohjelma ei lähetä kirjastolle äänidataa write- tms. funktiolla, vaan kirjasto pyytää ohjelmalta tarvittaessa lisää dataa
Entä rakenteinen data? edellä oletettiin, että datan sisällöstä ei välitetä; entä jos se otettaisiin huomioon rajapintaa suunnitellessa? tietoa lukeva funktio voisi palauttaa merkkijonon sijaan suoraan ohjelmointikielen rakenteen (esim. olion) tapahtumapohjaista mallia voisi helposti laajentaa niin, että eri tyyppisille viesteille olisi oma callback esimerkiksi graasissa käyttöliittymissä käyttäjän tekemisistä syntyy paljon eri tyyppisiä tapahtumia (erilaisia viestejä): nappia painettiin, ikkunan koko muuttui, jne. entä jos haluaakin käsitellä kaikkia viestejä samalla lailla? monimutkaisempiakin rajapintoja voisi tehdä: esim. lue seuraava nämä ehdot täyttävä alkio
Lopuksi GPS-esimerkistä sopiva rajapinta riippuu paljon siitä, mitä ohjelma tekee alussa olleesta esimerkistä: libgps-nimiseltä C-kirjastolta voi pyytää paikkadataa rekisteröimällä callback-funktio jota kutsutaan aina kun uusi paikkatietoa sisältävä paketti on saatu ja valitsemalla, millaista dataa haluaa (vähän muitakin yksityiskohtia tarvitaan) libgps:n rajapinta muuttui juuri tänä vuonna hyvien rajapintojen tekeminen on vaikeaa, ja voi mennä pitkäänkin ennen kuin tietää, kuinka toimivaksi uusi rajapinta osoittautui rajapinnan muuttaminen on työlästä, varsinkin jos moni eri ohjelma käyttää sitä
Sisältö 1 Laaja oppimäärä lyhyesti 2 Miten piirrän ohjelmassani kuvaajan datasta? 3 Miten luetaan ja kirjoitetaan dataa? 4 Tekstimuotoiset asetustiedostot kielinä 5 Rajapinnoista
Yksinkertaisia asetustiedostoja monissa ohjelmissa tekstimuotoiset asetustiedostot ovat kokoelma avainarvo-pareja, yleensä merkkijonoina esimerkki: ShowAnimations=Enabled (Konqueror-nimisestä WWW-selaimesta) näiden käsittelyyn käytetään usein valmiita kirjastoja esim. Windowsissa, Gnomessa ja KDE:ssa on omat usein samoja asetuksia voi muokata sekä graasella käyttöliittymällä että tekstitiedostoa editoimalla ohjelman sisällä asetukset voivat näkyä vaikka näin: lataaasetukset(), kutsutaan ohjelman käynnistyessä haearvo(asetuksennimi) palauttaa asetustiedostosta ladatun arvon tai oletusarvon muutaasetus(nimi, uusiarvo), tallenna() nimet ja arvot voivat olla merkkijonoja tai tietyn asetuksen arvo voi olla esim. kokonaisluku
Esimerkkejä avainarvo-pareista X Toolkit -kirjaston resurssit (X resources) XTerm-terminaaliohjelman puskurin koko XTerm*saveLines: 1500 Javan property-tiedostot (samanlaisia on muuallakin, mm. jo 90-luvulla Windows-ohjelmissa) Evince-PDF-lukijan asetuksista [Print Settings] scale=100 monet XML-muotoisista asetustiedostoista Gnome-työpöytäympäristön asetus <entry name="use_http_proxy" mtime="1136673836" type="bool" value="false"></entry>
Vähän monimutkaisempia asetuksia ssh:ssa asetuksiaan voi rajoittaa tietyille koneille OpenSSH-ohjelman asetustiedostosta Host *.hut.fi Compression yes X:n (Unixien graanen ympäristö) näppäimistökartoissa asetuksilla on enemmän kontekstia XKB-järjestelmän Suomen näppäimistökartasta hidden partial alphanumeric_keys xkb_symbols "fi" {... key <AE08> {[ 8, parenleft, bracketleft, guillemotleft ]}; Fvwm-ikkunamanagerissa voi tehdä omia funktioita ja laskea ikkunoiden koordinaateilla Fvwm-ohjelman asetuksia AddToFunc my-focus + I WarpToWindow 1p 1p + I Focus Key Tab A 4 Next (CurrentPage,!iconic) my-focus
Ohjelmointikieli asetustiedostossa asetukset voidaan tehdä myös oikeaksi ohjelmointikieleksi tarkoitetulla kielellä tällöin asetustiedoston ja lisämoduulin raja usein hämärtyy esimerkki: suuri osa Emacs-tekstieditorista on tehty sitä varten tehdyllä kielellä, Emacs Lispillä Lisp-kielen murre, johon lisätty tekstieditorin erikoisuuksia (esim. buer ja window) samalla kielellä tehdään sekä omia asetuksia että monimutkaista lisätoiminnallisuutta myös graanen asetuskäyttöliittymä yksinkertaisille asetuksille
Firefoxin asetukset ovat JavaScript-kieltä samaan tapaan kuin Emacsissa niissäkin on graanen editori (Preferences ja about:cong) JavaScriptiä käytetään WWW-sivuilla, mutta paljon myös Firefoxin sisällä Unixin ylläpidossa käytetään paljon asetustiedostoja, jotka luetaan komentotulkilla tai sisältävät tulkin komentoja esimerkki lisämoduulista: gnome-hearts-korttipeli on tehty C-kielellä, paitsi tietokonepelaajien tekoälyt Pythonilla (ennen vuotta 2006 Lua-kielellä) tekoälyn funktioilta kysytään, mitä tietokonepelaaja omalla vuorollaan haluaa tehdä tekoäly pääsee käsiksi lähinnä pelikorttien tietoihin ei ole tarkoitettu tavallisen käyttäjän muutettavaksi asetukseksi, vaan on selkeämmin lisäys itse ohjelmaan Laaja Kuvaaja Read/write Asetustiedostot Rajapinnoista Ohjelmointikieli asetustiedostossa: lisää esimerkkejä
Asetuksista ja kielistä yleisesti usein ohjelmissa on aluksi vain avainarvo-asetuksia... kunnes joku kaipaa muuttujia (50 % paperin koosta)... tai haluaa tehdä asetuksen joka on suhteessa johonkin toiseen (keskiarvo kahdesta muusta asetuksesta)... tai uuden toiminnon, joka muuttaa useampaa asetusta väliaikaisesti näin voi päätyä enemmän tai vähemmän ohjelmointikieltä muistuttavaan asetustiedostoon (esim. Fvwm) joskus asetuksille tehdään heti ohjelmointikieli (esim. Emacs, Firefox, komentotulkin asetustiedostot) mutta ohjelmointikieli on paljon monimutkaisempi ratkaisu kuin avainarvo-parit... tällaista erikoistunutta kieltä sanotaan DSL:ksi (domain-specic language, sovellusaluekohtainen kieli), jos se on (lähes) ohjelmointikieli
Sisältö 1 Laaja oppimäärä lyhyesti 2 Miten piirrän ohjelmassani kuvaajan datasta? 3 Miten luetaan ja kirjoitetaan dataa? 4 Tekstimuotoiset asetustiedostot kielinä 5 Rajapinnoista
Rajapinta on kieli eräs tapa ajatella rajapintoja on, että rajapinta antaa käyttäjälleen uuden kielen, jolla puhua tietokoneelle myös yksinkertaista luokkaa voi ajatella tällaisena useimmiten nämä kielet muistuttavat luonnollisia kieliä (joskus esim. matemaattista notaatiota) niistä voi löytää esim. verbejä, substantiiveja ja adjektiiveja; tai subjektin, objektin, jne. esim. progressbar.update(200, 15.0): subjekti, verbi, tarkentavia sanoja esim. stream.write("teksti"): subjekti, verbi, objekti
Millainen on hyvä rajapinta? hyvä rajapinta on muun muassa: intuitiivinen (eli muistuttaa käyttäjiensä osaamaa kieltä) helppo oppia kätevä käyttää (esim. ei vaadi käyttäjältä liikaa eikä liian vähän teknisiä yksityiskohtia) yhteensopivasti laajennettava monikäyttöinen (riippuu rajapinnasta?) tiivis (riippuu tilanteesta?) lisää pohdintaa ja hyviä esimerkkejä myöhemmin kurssilla...