Tämä kurssi on jo päättynyt.

Kurssin viimeisimmän version löydät täältä: O1: 2024

Luku 5.2: Katsaus Scala-työkalujen sisään

Tästä sivusta:

Pääkysymyksiä: Kuinka Scala toimii? Mitä Eclipse tekee huomaamatta puolestani? Miksi eräät laskutoimitukset toimivat hassusti?

Mitä käsitellään? Datan esittäminen (binaari)lukuina. Vähän tiedostoformaateista. Kääntäjä, virtuaalikone, tavukoodi ja konekieli. Roskankeruu. Lukuja kuvaavien tietotyyppien rajoituksia. Scala-työkaluja komentorivillä.

Mitä tehdään? Lähinnä luetaan. Jokunen minitehtävä.

Suuntaa antava työläysarvio:? Tunti.

Pistearvo: A5.

Oheisprojektit: Ei ole.

../_images/person03.png

Johdanto: Scala-koodin vaiheet

Pidetään pieni tauko uusien käskyjen opettelussa. Tässä luvussa tutkimme, mitä vaiheita liittyy siihen, että ohjelmakoodi muuttuu konkreettiseksi toiminnaksi tietokoneessa. Seuraava esitys johdattelee aiheeseen:

../_images/mammuttikirja.png

Tämän luvun kuvitus on kunnianosoitus klassikkokirjalle.

Muun muassa Eclipse-sovelluskehitin huolehtii esityksessä mainituista vaiheista automaattisesti ja huomaamattomasti. Eclipsekin kyllä käyttää useita eri työkaluja, esimerkiksi kääntäjää ja virtuaalikonetta, joiden yhteistoiminta vain on siinä määrin saumatonta, ettei Eclipsen käyttäjän tarvitse ajatella (tai aina edes tietää) käyttävänsä useaa työkalua. Nämä vaiheet onkin tähän mennessä kurssilla jätetty puhtaasti sovelluskehittimen huoleksi. Kuitenkin nyt perehdymme tähän aiheeseen hieman.

Miksi?

Jos kerran vaikkapa Eclipse tekee asiat automaattisesti, niin miksi niistä pitäisi nyt oppia?

Ensinnäkin on huomattava, että ohjelmointia voi tarkastella useilla eri abstraktiotasoilla. Esimerkiksi olio-ohjelman suoritusta voi kuvata korkealla abstraktiotasolla olioiden viestintänä. Tämä perustuu matalammalla tasolla ohjelmointikielen käskyjen suorittamiseen peräjälkeen. Vielä matalammalla tasolla kyse on bitteihin liittyvistä operaatioista prosessoreissa.

../_images/mammoth_in_scala.png

Tietyllä abstraktiotasolla työskennellessä on avuksi se, että ymmärtää jonkin verran myös alla olevasta tasosta. Kyvystä katsoa abstraktion sisään on apua erityisesti ongelmatilanteiden ratkomisessa, kun käyttämämme käsky ei toimikaan kuten halusimme tai luulimme.

Sama pätee ohjelmoijan apuohjelmiin. Sovelluskehittimen käytössä on avuksi ymmärtää jotain sen sisäisestä toiminnasta.

Toinen hyvä syy on se, että "oikeassa maailmassa" kurssin ulkopuolella ohjelmoidaan muuallakin kuin Eclipsessä. Työkalun taustalla olevia perusperiaatteita on siis tarpeen hahmottaa, jotta voi joustavammin vaihtaa työkaluja tarvittaessa.

Aiheeseen perehtyminen on perusteltua jo tietoteknisen yleissivistyksenkin vuoksi.

Ja tietysti siksikin, että joidenkin ohjelmoijien on kyettävä kehittämään eclipsejä ja vastaavia ohjelmoijien käyttöön. (Mutta se ei ole tämän luvun tai kurssin aihe.)

Katsotaan siis, kuinka Scala toimii.

Erillisiä työkaluja

Yksittäisiä Scala-koodin työstämisen vaiheita on helpompi hahmottaa, kun käytämme sovelluskehittimen sijaan irrallisia työkaluja.

Scala-kielen viralliseen työkalupakkiin (ks. http://www.scala-lang.org/) kuuluu joukko toisistaan erillisiä työkaluohjelmia:

  • Kääntäjäohjelma nimeltä scalac (Scala compiler).
  • Virtuaalikoneen käynnistysohjelma nimeltään yksinkertaisesti scala.
  • Dokumentaation tuottamistyökalu Scaladoc (luku 3.1).
  • Muita osia, joita ei käsitellä tässä.

Seuraava esitys näyttää, miten luodaan ja ajetaan pikkuruinen Scala-ohjelma käyttäen editoria, kääntäjää ja virtuaalikonetta kutakin erikseen.

Onko komentorivi tuttu?

Esityksen seuraamisessa auttaa, jos työskentely komentoriviympäristössä on edes vähän tuttua — siis tekstikäskyjen käyttö esimerkiksi Windowsin Command Promptissa tai Linuxin terminaali-ikkunan komentotulkissa. Jos ei ole, löydät aiheesta ohjeita netistä. Esityksen pääpointit avautunevat kyllä kokemattomallekin.

Alempana on kerrottu lisää näistä työvaiheista ja -kaluista sekä siitä, miten vaiheet toteutuvat Eclipseä käyttäessä.

Koska kyse on tiedon muokkaamisesta eri muotoihin, tarvitset vähän pohjatietoja siitä, missä muodoissa tietoa voidaan tietokoneessa esittää. Aloitetaan siis biteistä.

Luvut bitteinä, data lukuina

Binaariluvut muodostuvat ykkösistä ja nollista. Yhtä binaariyksikköä — nollaa tai ykköstä — kutsutaan bitiksi (bit eli binary digit).

Esimerkiksi kymmenjärjestelmän lukuja voi kuvata bitteinä seuraavasti:

14kymmen = 8 + 4 + 2 = 1*23 + 1*22 + 1*21 + 0*20 = 1110binaari

5kymmen = 4 + 1 = 1*22 + 0*21 + 1*20 = 101binaari

Tietokone on digitaalinen järjestelmä. Sen muistissa kaikki data on esitetty binaarilukuina tai muuten bittijonoina.

Kuvat ja teksti bitteinä

Kuva on väripisteiden eli pikselien yhdistelmä. Pisteiden värisävyt voidaan ilmaista lukuina ja nämä luvut bitteinä.

Teksti puolestaan muodostuu kirjoitusmerkeistä. Voidaan sopia, että kutakin erilaista merkkiä vastaa yksiselitteisesti jokin luku. Scalassa merkkijonojen kuvaamiseen käytetty Unicode (http://www.unicode.org/) on eräs tällainen merkistösopimus eli -standardi. Alla on esimerkkejä Unicode-standardin määräämistä lukuarvoista eräille merkeille:

Unicode-arvo Merkki
32 välilyönti
38 &
48 0
49 1
65 A
66 B
97 a
98 b
122 z
945 α (alfa)

Kun meillä on merkille lukuarvo, voimme esittää merkin bitteinä (tavalla tai toisella, esim. suora binaarilukuesitys kuten yllä). Merkkijono voidaan siis kuvata jonona binaarilukuja.

Viittaukset bitteinä

Viittauksen käsite on jo tullut tutuksi: pieni datajyvänen, jonka perusteella voidaan käsitellä toista dataa (eli jotakin oliota). Kurssimateriaalin animaatioissa olet nähnyt olioihin kohdistuvia viittauksia, joita on esimerkiksi tallennettu muuttujiin ja välitetty parametreiksi. Pohjimmiltaan Scala-ohjelmassa esiintyvä viittauskin on bittijono (binaariluku), joka tavalla tai toisella identifioi tietokoneen muistista kohdan, josta löytyvät tietyn olion tiedot.

Ohjelma bitteinä

Ei vain ohjelman käsittelemä data vaan myös itse ohjelma pidetään tallessa bitteinä, yleensä tiedostoon tallennettuna. Itse asiassa: ohjelmakin on dataa ja ohjelmat voivat käsitellä ohjelmia!

Scalan tapauksessa ja muutenkin useimmiten ohjelman lähdekoodi on ns. plain text -muodossa, joka koostuu vain kirjoitusmerkkien bittikuvauksista. Kääntäjällä ohjelma muutetaan toiseen muotoon, ja samalla myös tapa, jolla ohjelma on kuvattu bitteinä, muuttuu.

Palataan ohjelmoijan aputyökaluihin. Aloitetaan lähdekoodista ja editoreista.

Tekstieditoreista

../_images/mammutti_ja_sorsa.png

Plain text -tekstitiedosto — tai lyhyemmin sanoen vain "tekstitiedosto" — on tiedosto, johon on tallennettu bittien sarja, jonka pätkät vastaavat lukuja, jotka puolestaan vastaavat kirjoitusmerkkejä jonkin tietyn standardin mukaisesti. Näitä tiedostoja voi lukea ja muokata sovelluksilla, jotka on laadittu niin, että ne osaavat tulkita tiedostoon tallennetut bitit merkeiksi. Tällaisia ohjelmia ovat esimerkiksi Eclipsen editori, Emacs, Windowsin Notepad ja Macien TextEdit.

Muunlaiset tiedostoformaatit noudattavat eri standardeja kuin kuvatiedostot (esim. Excel-taulukko, GIF-kuvatiedosto, Word-dokumentti). Niiden käsittelyyn tarvitaan toisia ohjelmia, jotka osaavat käsitellä niihin tallennetut bitit tiedostoformaatin mukaisella logiikalla.

Voit tutkia itsekin

Jos haluat, voit avata esimerkiksi Word-dokumentin Notepadissä (tai muun tiedoston, joka ei ole plain text -muodossa, jossakin muussa tekstitiedostoille tarkoitetussa editorissa). Koska Word-dokumenttitiedoston sisältämät bitit eivät kuvaa ainoastaan kirjoitusmerkkejä vaan myös erilaisia kirjasinasetuksia ja muita muotoiluja ja lisätietoja, ei Notepad osaa tulkita niitä oikein. Se kuitenkin tulkitsee bitit kuten parhaiten osaa eli merkkeinä. Tällöin editorissa näkyy "sotkua".

Koodia voi Eclipsen sijaan kirjoittaa missä tahansa muussakin tekstitietoja tallentavassa ohjelmassa. Esimerkiksi Notepad on täysin mahdollinen koodausympäristö; se ei vain muilta ominaisuuksiltaan tue ohjelmointia hyvin, joten juuri kukaan ei halua sitä ohjelmointiin käyttää. On olemassa monia sovelluskehittimistä irrallisia tekstieditoreja, joissa on ohjelmakoodin editointia helpottavia ominaisuuksia.

Eclipsellä kirjoitetut kooditiedostot tallennetaan koneen tiedostojärjestelmään aivan samaan tapaan kuin muidenkin sovellusten tuottamat tallenteet. Kooditiedostot löytyvät Eclipse-työkansion alta pakkausten mukaan nimetyistä alikansioista. Sieltähän oletkin valinnut niitä palautettaviksi A+:n arvosteltaviksi.

Konekielestä

Scala on korkean tason ohjelmointikieli (high-level language). Tämä tarkoittaa sitä, että kielen abstraktiotaso on korkea eivätkä sen ilmaisut ole suoraan sidoksissa esimerkiksi binaarilukuihin ja tietokoneen fyysisten osien toimintaan.

Minkään normaalin tietokoneen prosessori ei osaa Scalaa eikä mitään muutakaan korkean tason ohjelmointikieltä. Prosessorit aikaansaavat tietokoneiden toiminnan suorittamalla suuria määriä alkeellisia käskyjä, jotka on kirjoitettu konekielellä (machine code tai machine language).

Konekielen käskyt ovat bittijonoja, jotka vastaavat matalan abstraktiotason toimintoja kuten binaariluvuilla laskemista, bittien vertailua, nollien vaihtamista ykkösiksi ja muuta hyvin alkeellista toimintaa. Eri prosessorimalleilla on omat konekielensä, joissa erilaiset bittijonot vastaavat erilaisia käskyjä.

Konekieliohjelmointi on työlästä ja altista virheille. Hyvin suurten ohjelmistojen laatiminen konekielellä on käytännössä mahdotonta; voit verrata tätä vaikkapa täyskokoisen pilvenpiirtäjän rakentamiseen Lego-palikoista.

Kääntäminen, välikielet ja tavukoodi

Ohjelmoijan laatima lähdekoodi pitää muuntaa muotoon, joka soveltuu tietokoneen ajettavaksi.

../_images/mammutti_kaantaa_sorsan.png

Perinteisesti on erotettu kaksi tapaa muuntaa korkean tason kieltä suoritettavaan muotoon. Kääntämiseksi (compiling) sanotaan ohjelman muuntamista ennen kuin se määrätään suoritettavaksi. Tulkkaamiseksi (interpreting) taas sanotaan ohjelman suorittamista siten, että muunnos konekieleksi tehdään vähitellen suorittamisen yhteydessä.

Ensimmäinen Scala-ohjelmien käsittelyyn liittyvä vaihe on kääntäminen.

Kääntäminen on systemaattinen prosessi, ja se voidaan tehdä koneellisesti apuohjelmalla. Kääntämisen tuloksena syntyy koneen luettavaksi tarkoitettua koodia (ns. "binäärejä"). Kääntäessä samalla varmistetaan etukäteen, että ohjelma on kieliopin mukainen.

Eclipsellä on käytössään kääntäjäapuohjelma. Normaaliasetuksilla Eclipse kääntää Scala-lähdekoodia toistuvasti ja automaattisesti samalla kun koodia muokataan. Osa virheilmoituksista syntyy juuri käännöstyön yhteydessä. (Muista: aiemmissa luvuissa on ollut puhetta käännösaikaisista virheilmoituksista.)

Monia ohjelmointikieliä käännetään suoraan aidoksi konekieleksi ja annetaan käyttäjille konekielisessä muodossa. Esimerkiksi Windowsista tutut .exe-tiedostot sisältävät konekieltä, joka on yleensä saatu jostakin korkean tason kielestä kääntämällä. Kuitenkaan tämä ei ole ainoa vaihtoehto:

Välikielet ja tavukoodit

Viime vuosikymmeninä yleistynyt vaihtoehto suoraan aidoksi konekieleksi kääntämiselle on käyttää välikieltä (intermediate language). Syistä lisää alempana; perusajatus on seuraava:

  • Käytetään kääntäjätyökalua, jolla muunnetaan lähdekoodi välikielelle ennen ohjelman ajamista.
  • Käytetään ajamisessa apuna sellaista apuohjelmaa, joka osaa muuntaa välikielisen ohjelman konekieleksi. Tämä muunnos tehdään ohjelma-ajon yhteydessä, ja tietokone suorittaa konekielisen version.

Esimerkiksi Scala-ohjelmat käännetään aluksi välikielelle.

Joistakin välikielistä käytetään nimitystä tavukoodi (bytecode), niin myös siitä välikielestä, joksi Scala-ohjelmia tavallisesti käännetään. Scala-kääntäjä tuottaa class-päätteisiä luokkatiedostoja, jotka sisältävät tavukoodia. Yhdestä .scala-tiedostosta syntyy kääntäessä yksi tai useampia .class-tiedostoja.

Etsi omat tavukooditiedostosi

Selaa oman kurssikansiosi sisältöä. Huomaat projektikansioiden alla bin-nimisiä kansioita. Kyseessä ei ole mikään roskis (garbage bin) vaan lyhenne sanasta binaries. Kansio sisältää Eclipsen tuottamat käännetyt versiot projektin Scala-koodista class-päätteisissä tavukooditiedostoissa.

Tavukoodi koostuu varsin alkeellisista käskyistä ja muistuttaa konekieltä. Konekielen tavoin ja lähdekoodista poiketen tavukoodi ei ole tarkoitettu ensisijaisesti ihmisen luettavaksi tai muokattavaksi. class-päätteiset tavukooditiedostot eivät ole tekstitiedostoja, eivätkä tekstieditorit osaa niiden sisältöä mielekkäällä tavalla näyttää.

../_images/notepad_class.png

Eräs tavukooditiedosto tarkasteltuna Notepad-tekstieditorissa, joka on tähän tarkoitukseen kelvoton.

Tavukooditiedoston hyödyntämiseen tarvitaan siihen erikoistunut ohjelma. Tärkein tällainen ohjelma on virtuaalikone.

Virtuaalikoneet ja JVM

../_images/mammoth_virtual.png

Sanalla virtuaalinen (virtual) on monia merkityksiä, muiden muassa nämä:

  • keinotekoinen,
  • matkittu,
  • simuloitu,
  • ei todellinen vaikka todellista muistuttaakin,
  • ikään kuin — vaikkei varsinaisesti tai virallisesti — tietynlainen.

Virtuaalikone on tietokoneohjelma, joka toimii ikään kuin tietokone olematta kuitenkaan oikeasti fyysinen tietokone.

Kun valitset Run As ‣ Scala Application ja näin käsket Eclipseä ajamaan ohjelman, vaikuttaa pinnallisin puolin siltä kuin Eclipse ottaisi Scala-kooditiedoston ja ajaisi sen. Eclipse ei kuitenkaan oikeastaan itse aja ohjelmaasi, vaan käynnistää kulissien takana apuohjelman — virtuaalikoneen, joka huolehtii ohjelman suorittamisesta. Eclipse antaa virtuaalikoneelle ajettavaksi tavukoodiksi käännetyn version kirjoittamastasi Scala-lähdekoodista ja välittää virtuaalikoneelle tiedon siitä, mistä kohdasta ohjelmaa kuuluu lähteä suorittamaan (valitsemasi käynnistysolion perusteella). Eclipse toimii siis samantapaisesti kuin tehtiin manuaalisesti komentorivin scala Ave -käskyllä yllä näkemässäsi esityksessä.

Käyttämäsi virtuaalikone on ikään kuin tietokone, jonka prosessori osaa suorittaa tavukoodikäskyjä; sen virtuaalista konekieltä on tavukoodi. Virtuaalikone määrittää, mitä todellisia konekielikäskyjä kukin tavukoodikäsky vastaa ja välittää ne todellisen tietokoneen suoritettavaksi. Käytännössä siis kyseinen ohjelma tulee ajetuksi.

Virtuaalikone hallinnoi ohjelman ajamista. Se huolehtii ohjelman suorituksen etenemisestä ja muistiresurssien hallinnasta. Ne lukuisat animaatiot ohjelmien suorituksesta, joita olet kurssimateriaalissa nähnyt, kuvaavat juuri eräitä virtuaalikoneen vastuualueelle kuuluvia seikkoja: kutsupinon kehysten luominen ja poistaminen, olioiden luominen muistiin ja niin edelleen.

Yleisintä Scala-ohjelmien ajamiseen käytettyä virtuaalikonetta sanotaan JVM:ksi. Tämä on lyhenne sanoista Java Virtual Machine eli Java-virtuaalikone.

Hetkinen! Java-virtuaalikone?!

../_images/mammoth_in_java.png

Eikö Java ole erään ihan toisen ohjelmointikielen nimi?

Onhan se, mutta silti Scala-ohjelmia ajetaan Java Virtual Machine -nimisellä apuohjelmalla.

Java-virtuaalikone on ohjelma, jolla voi ajaa tietynlaiseksi tavukoodiksi käännettyjä ohjelmia. Alun perin kyseinen tavukoodityyppi kehitettiin Java-kielistä ohjelmointia varten, ja siksi virtuaalikonetta sanotaan JVM:ksi. Kuitenkin tuota virtuaalikonetta voi käyttää minkä tahansa sellaisten ohjelmien ajamiseen, jotka on käännetty sen kanssa yhteensopivaksi tavukoodiksi, riippumatta siitä, millä kielellä lähdekoodi on kirjoitettu.

Java-virtuaalikoneella voi siis ajaa vaikkapa Scala-ohjelmia, jos ne vain on käännetty sopivalla kääntäjällä .class-tiedostoiksi. Nykyään JVM on suosittu alusta paitsi Javalle myös sitä kehittyneemmille ohjelmointikielille, joista Scala on yksi esimerkki.

Myös yllä komentoriviesimerkissä annettu scala Ave -käsky käynnisti nimestään huolimatta nimenomaan Java-virtuaalikoneen. Käsky ei olisi toiminut, jos tietokoneelle ei olisi asennettu Scala-työkalujen lisäksi myös Java-virtuaalikonetta.

Scala, Java, JavaScript

Käytetyin Scala-toteutus, jota käytämme tälläkin kurssilla, on rakennettu juuri "Javan päälle". Käyttämiemme Scala-kielen kirjastojen toteutuksessa on käytetty apuna olemassa olevia Java-kielen laajoja kirjastoja. Onkin niin, että Java paistaa läpi Scala-ohjelmoinnissa sieltä täältä. Olet ehkä kiinnittänytkin huomiota esimerkiksi seuraaviin asioihin.

  • Kurssimateriaalin ohjeissa Eclipsen ja Scalan asentamiseksi ohjeistetaan nappaamaan netistä Scalan lisäksi myös Java.
  • Kunkin Scala-projektin sisällä näkyy Eclipsen Package Explorerissa sekä merkintä Scala Library että merkintä JRE System Library. JRE eli Java Runtime Environment sisältää JVM:n ja Javan peruskirjastot.
  • Luvussa 4.1 saimme ilmoituksen NullPointerException-virheestä. Tarkemmin sanoen virheilmoituksessa luki java.lang.NullPointerException, mikä johtuu siitä, että kyseinen virhetyyppi on määritelty Java-kielen peruskirjastossa ja Scala-kirjastot uudelleenkäyttävät sitä.
  • Javan kahvikuppilogo esiintyy esimerkkisovellusten GUI-ikkunoiden yläkulmassa.

Scala-kielen määrittely ei kuitenkaan sinänsä ole Java-riippuvainen eikä estä muunlaisia toteutuksia Scala-kirjastoille ja -työkaluille. Sellaisia onkin kehitetty. Tärkein vaihtoehtoinen toteutus on Scala.js, joka nojautuu JavaScript-kieleen ja mahdollistaa Scala-ohjelmien ajamisen web-selaimessa.

Perusteluja välikielelle ja virtuaalikoneelle

Mitä hyötyä on siitä, että lähdekoodi käännetään välikielelle, joka sitten suoritetaan virtuaalikoneen avulla? Eikö olisi suoraviivaisempaa ja helpompaa kääntää suoraan konekielelle?

Vertaillaan eräillä keskeisillä kriteereillä:

Laatukriteeri Välikieli & virtuaalikone Suoraan konekieleksi
siirrettävyys (portability) Välikielelle käännettyjä ohjelmia voi käyttää missä tahansa ympäristössä, jossa on tarjolla kyseiseen ympäristöön sopiva virtuaalikone. Esimerkiksi JVM:lle käännettyjä Scala-ohjelmia voi suorittaa millä tahansa alustalla, jolle on tehty JVM-toteutus. (Tämä on osin myös haitta: virtuaalikone on oltava.) Ohjelmia voi suorittaa vain tietynlaisessa ympäristössä, jossa kyseinen konekieli toimii. Eri ympäristöihin pitää tuottaa erilaiset käännökset ohjelmasta.
yhdisteltävyys (interoperability) Samalle välikielelle voidaan kääntää useita kieliä. Tämä mahdollistaa eri kielillä kirjoitettujen ohjelman osien toimimisen yhteen. Voidaan myös käyttää yhden kielen kirjastoja toisesta kielestä. Esimerkiksi JVM:ssä ajettavista kielistä voi käyttää Java-kielen laajoja kirjastoja. Eri kielillä kirjoitettujen ohjelmakomponenttien sovittaminen yhteen vaatii erityisjärjestelyjä.
suoritustehokkuus (efficiency) Välikielen käytöstä seuraa jonkin verran tehokkuushaittoja kuten ohjelmien hidastumista. Haittoja voidaan lieventää erilaisilla optimoinneilla. Kun tietty virtuaalikone on laajasti käytetty (kuten JVM on), niin siihen tehdyistä optimoinneista hyötyvät usealla eri kielellä lähdekoodinsa kirjoittavat ohjelmoijat. Kääntäjäohjelma on mahdollista laatia nojaten nimenomaisen lähdekieli–konekieli-parin ominaisuuksiin. Käännöksenä saadaan tehokkuusoptimoitua konekieltä. Riippuu tilanteesta, onko näin saavutettu tehokkuusetu käytännössä merkityksellinen.

Virtuaalikoneet ovat nykyään aiempaa suositumpia, ja kehitys näyttäisi jatkuvan samaan suuntaan.

Tavukoodin suorittamisesta

Joidenkin erikoistietokoneiden laitteisto tukee JVM-tavukoodin suorittamista "suoraan". Useimpien kuitenkaan ei, vaan tavukoodi muunnetaan ensin konekieleksi.

Roskaa virtuaalikoneessa

Olion voi luoda siten, että sitä tarvitaan vain vähän aikaa, minkä jälkeen sillä ei enää tehdä ohjelmassa mitään. Olio voi olla käytössä esimerkiksi vain yhdellä ohjelman rivillä. Esimerkki: Mitä tapahtuu luodulle puskurioliolle tällaisen tulostuskäskyn suorittamisen jälkeen?

println(Buffer(5, 2, 7, 32, 9)) // Huom. viittausta puskuriin ei jää mihinkään talteen
// tänne muita käskyjä, jotka eivät käytä äsken luotua puskuria

Olioita, jotka "leijuvat" muistissa ilman, että niihin pääsee ohjelmasta käsiksi, kutsutaan roskaksi (garbage). Roska vain vie muistitilaa turhaan.

Ohjelman tulisi vapauttaa roskalle varattu muisti muuhun käyttöön jo ohjelma-ajon aikana. Aina näin ei kuitenkaan käy. Klassinen bugityyppi on muistivuoto (memory leak): ohjelma varaa itselleen käyttöön tilaa tietokoneen muistista, mutta ei vapautakaan varaamaansa muistia myöhemmin, kun ei enää tarvitse sitä. Vähitellen vuotava ohjelma saattaa varata aina vain enemmän muistia kunnes resurssit ehtyvät ja ohjelma kaatuu.

Muistinhallinnasta

Joissakin ohjelmointiympäristöissä ohjelmoija huolehtii muistiresurssien vapauttamisesta nimenomaisin käskyin. Esimerkiksi C++ -ohjelmointikielellä olion voi luoda seuraavasti. Tämä käsky, joka varaa olion tiedoille muistia ja alustaa olion, muistuttaa aika lailla Scalaa:

JokuLuokka* testi = new JokuLuokka();    // Tämä ei ole Scalaa vaan C++:aa.
var testi = new JokuLuokka               // Tämä on vastaava Scala-käsky.

Kun oliota ei enää tarvita, C++ -ohjelmoija voi poistaa testi-muuttujan osoittaman olion muistista näin:

delete testi;                            // Tämä ei ole Scalaa vaan C++:aa.

Näin tarpeeton olio ei jää roskaksi tietokoneen muistiin.

Emme ole oppineet deleteä vastaavaa Scala-käskyä. Eikä sellaista olekaan.

Jos Scala-ohjelma on käynnissä pitkään, ja uusia olioita luodaan lisää, niin viekö se aina vain enemmän ja enemmän muistia?

Ei:

Automaattinen roskankeruu

../_images/mammoth_collects_garbage.png

Java-virtuaalikoneen toimenkuvaan kuuluu automaattinen roskankeruu (garbage collection). Virtuaalikone valvoo sitä, mitkä luoduista olioista ovat roskaa, ja vapauttaa roskalle varatun muistin muuhun käyttöön. Ohjelmoijan ei tarvitse puuttua asiaan.

JVM katsoo roskaksi kaikki sellaiset oliot, joihin osoittavia viittauksia ei ole enää mihinkään tallennettuna.

Automaattinen roskankeruu ehkäisee ison osan vuotovaarasta. Silti esimerkiksi Scala-ohjelmakaan ei ole immuuni muistivuodoille. Esimerkki: Ohjelma säilöö puskuriin viittauksia, jotka osoittavat olioihin. Jos näitä olioita ei poisteta puskurista, kun niistä on tullut ohjelman toiminnan kannalta tarpeettomia, niin JVM:n roskankerääjä ei siivoa niitä pois. Kuitenkin jos puskuriolio itse on roska, eikä siihen tallennettuihin olioihin viitata muualta kuin puskurista, niin myös puskurin sisältö tulkitaan roskaksi ja tulee kyllä automaattisesti siivotuksi. On ohjelmoijan vastuulla laatia ohjelma siten, että se ei jätä turhia viittauksia tallennetuiksi muuttujiin tai kokoelmiin.

Lisämateriaalia

Miten voin jakaa Scalalla kirjoitetun sovellukseni toisten käytettäväksi?

Vaihtoehtoja on monta. Niistä lyhyesti alla ja lisää keväisellä Ohjelmointistudio 2 -kurssilla.

  1. Jos käyttäjän koneella jo on Java-virtuaalikone (JVM) peruskirjastoineen, niin helpohko vaihtoehto on paketoida ohjelma suoritettavaksi JAR-tiedostoksi (executable JAR). Se on tiedosto, johon paketoidaan sekä lähdekoodista käännetyt .class-tiedostot että Scalan vaatimat kirjastot. Lisäksi tiedostoon kirjataan tieto siitä, mistä ohjelma lähtee käyntiin. Suoritettavien JAR-pakettien muodostaminen onnistuu esimerkiksi SBT-työkalulla (Simple Build Tool).

  2. ../_images/scala_js.png

    Scala.js:n logo.

    Scala.js. Scalaa voi nyttemmin kääntää JVM-tavukoodin sijaan myös JavaScript-kieleksi, joka on nimestään huolimatta ihan eri asia kuin Java ja jota nykyaikaiset web-selaimet osaavat suorittaa. Tämä onnistuu Scala.js-työkalulla.

  3. Natiivikääntäjät tuottavat tavukoodin sijaan tiettyyn ajoympäristöön sopivia käännöksiä; eri ympäristöihin täytyy tuottaa omat versionsa. Käännöksen mukaan voidaan liittää kirjasto, joka huolehtii mm. roskankeruusta. Scalaa ei yleensä käännetä natiivikääntäjällä, mutta sekin onnistuu:

    • Scala Native on tuore Scala-natiivikääntäjä. Se perustuu LLVM-kääntäjätyökaluihin.
    • GCJ-kääntäjä tuottaa .class-tiedostojen perusteella suoritettavia konekielisiä ohjelmia, jotka eivät tarvitse Java-suoritusympäristöä. Tämä toimii Scalalla tuotetulle tavukoodille siinä missä muillekin Java-virtuaalikoneen kanssa yhteensopiville ohjelmille.
  4. "Java-wrapper" on ohjelma, jossa käännetty Java- tai Scala-koodipaketti on "kääritty" pienen suoritettavan ohjelman mukaan. Ajettaessa apuohjelma joko käyttää järjestelmään jo asennettua Java-virtuaalikonetta sovelluksen suorittamiseksi tai asentaa tarvittavan ympäristön.

  5. Java Web Start on tekniikka, jossa ohjelman käynnistys tapahtuu käytännössä klikkaamalla linkkiä web-sivulla. Java Web Start -työkalu huolehtii ohjelman ja sen vaatiman Java-virtuaalikoneen asennuksista ja lopulta suorittaa ohjelman.

Osaa yllä mainituista vaihtoehdoista on esitelty tarkemmin esimerkiksi seuraavissa lähteissä:

Nettilähteitä tutkiessa on syytä olla tarkkana, koska — kuten sanastosivummekin kertoo — "Java"-sanalla voidaan viitata joko Java-kieleen tai (Scala-ohjelmoijan kannalta relevantimmin) JVM:ään ja siihen liittyviin teknologioihin, eivätkä kirjoittajat aina selvästi ilmaise, kumpaa tarkoittavat.

Lopuksi on syytä mainita, että yleisiä ovat myös sellaiset web-sovellukset, joiden sisäisestä toimintalogiikasta huolehtii palvelinkoneessa ajettava ohjelma, vaikka käyttöliittymä toimii loppukäyttäjän omassa web-selaimessa. Scalaa käytetään myös tällaisten palvelinsovellusten laatimiseen, jolloin riittää, että tarvittavat työkalut (JVM yms.) on asennettu palvelinkoneelle eikä loppukäyttäjälle. Web-palvelimien ohjelmoinnista kerrotaan jatkokursseilla.

JVM eri ympäristöissä

Miten laiteriippuvainen JVM siis oli? Löydänkö sellaisen PC:stä, mäkistä ja Mersun E-sarjan autoista?

JVM-toteutuksia on olemassa erittäin moniin erilaisiin ympäristöihin. Niitä löytyy paitsi erilaisista pöytäkoneista myös pienistä sulautetuista laitteista sekä palvelinfarmeista, joilla pyöritetään isoja verkkosivustoja. Vähäresurssisissa sulautetuissa järjestelmissä käytetään usein kevennettyä Java-versiota, mutta monet nykyisin käytetyt sulautetut järjestelmät pyörittävät samaa Java-versiota kuin pöytäkoneetkin.

Elikkä JVM:n avulla voisi vaikka suorittaa [Scala- tai Java-ohjelmia] parkkilippukoneella?

Kyllä. Parkkilippukone on Oracle-yhtiön Java-mainoksissa toistuva esimerkki.

Java Technology can be found in a broad spectrum of products across a diverse set of industries that produce anything from RFID readers to parking meters to ATMs to in-flight video systems to POS terminals to wearable systems (just to name a few) — — across a large number of hardware and OS platform configurations.

—Javan takapiruna toimivan Oracle-yhtiön mainosteksti

JVM ei ole ainoa virtuaalikonetyyppi, jolla voi suorittaa Java- tai Scala-kielellä kirjoitettuja ohjelmia. Mm. Android-puhelimissa on JVM-standardista poikkeava virtuaalikonetoteutus.

Kaikissa yhteyksissä JVM:n kaltainen virtuaalikone ei ole hyvä ratkaisu. Tässä yksi esimerkki:

Aikoinaan opin, ettei [Javaa] saa käyttää ydinvoimaloissa tai muissa suurta turvallisuutta vaativissa kohteissa. Syy liittyi jotenkin roskien keruuseen. Koskeeko sama Scalaa?

Kyllä. Taustalla toimiva roskankeruu vie silloin tällöin aikaa — vain hetken, mutta kuitenkin. Tämä yhdessä muiden seikkojen kanssa tekee vaikeaksi tai mahdottomaksi ennustaa erittäin tarkasti, kuinka kauan tietyn ohjelma-ajon vaiheen suorittaminen kestää. Ydinvoimalan kaltaisissa elintärkeissä järjestelmissä (life-critical system) tällainen ratkaisu ei kelpaa.

Elintärkeiden järjestelmien tarpeisiin on omia käyttöjärjestelmiään ja ohjelmointikieliään (ks. esim. real-time operating system, real-time computing, SPARK-ohjelmointikieli). Niissä käytetään muodollisempia ja varmempia työskentelytapoja kuin tavanomaisessa ohjelmointityössä (ks. esim. model checking, formal verification).

Voiko käännettyä ohjelmaa kääntää takaisin lähdekoodiksi?

Periaatteessa onnistuu ja osin käytännössäkin.

Kun käännetään korkean tason kielestä konekieleksi, osa lähdekoodin sisältämästä informaatiosta menetetään. Esimerkiksi muuttujien nimiä, kommentteja ja korkean tason kielen muita rakenteita, jotka jäsentävät kokonaisuutta, voidaan menettää.

Takaisinpäin käännetty koodi voi käännettävästä ohjelmasta, ohjelmointikielestä ja kääntäjistä riippuen olla erittäinkin vaikeaselkoista. Koodi ei välttämättä ole ymmärrettävissä niin helposti, että yritys olisi käytännössä vaivan arvoista.

On olemassa työkaluja, joilla takaisin päin kääntämistä voi tieten taiten hankaloittaa.

Hakusanoja: decompiler, reverse engineering, code obfuscation.

Avoin lähdekoodi

Avoin lähdekoodi (open source) on jälleen yksi termi, jonka täsmällinen merkitys vaihtelee puhujasta riippuen. Keskeistä joka tapauksessa on, että ohjelman lähdekoodi on julkisesti saatavilla ja että sallitaan muidenkin kuin alkuperäisen tekijän hyödyntää sitä omissa ohjelmistoprojekteissaan.

On paljon erilaisia avoimen lähdekoodin lisenssejä, joilla koodin voi julkaista. Lisenssit eroavat toisistaan esimerkiksi sen suhteen, sallitaanko kaupallinen käyttö, ja vaaditaanko, että hyödyntäjä myös vastaavasti julkaisee oman alkuperäistä versioivan tai muuten käyttävän ohjelmansa.

Lähdekoodin avoimuus voi helpottaa ohjelmistojen kehittämistä eri tahojen — firmojen ja/tai yksityishenkilöiden — yhteistyössä. Lähdekoodin parissa työskentelevä yhteisö voi esimerkiksi ideoida ja toteuttaa muutoksia koodiin sekä arvioida yhteisön jäsenten tuotoksia laadun varmistamiseksi.

Avoimeen lähdekoodiin liittyy myös arvoja ja ideologioita. Lisää niistä ja teemasta muutenkin voi lukea esimerkiksi Wikipediasta.

Avoimen lähdekoodin projekteja on vähintään satoja tuhansia. Kuuluisin niistä lienee suomalaislähtöinen Linux-käyttöjärjestelmä. Muita esimerkkejä ovat ilmaisten ohjelmistojen puolesta liputtavan Mozilla-järjestön selain Firefox ja sähköpostiohjelma Thunderbird. Ja Eclipse. Ja Scala.

Kunkin Scala API:n luokkaa kuvaavaan Scaladoc-sivun alkupäässä on Source-linkki, jonka kautta voit tarkastella kyseisen luokan koodia. Scalan lisenssi on hyvin salliva.

Kääntäminen vs. tulkkaaminen

Tulkkauksen ja kääntämisen raja on hämärä, erityisesti nyttemmin. Aina ei ole helppoa — tosin ei ehkä tarpeellistakaan — sanoa kummasta on kysymys. Välikielten ja virtuaalikoneiden käyttö sotkee asiaa osaltaan, minkä lisäksi soppaa hämmentää ajonaikainen kääntäminen (just-in-time compilation eli JIT), josta voit hakea lisätietoa internetistä vapaaehtoisena lisäharjoituksena.

Joihinkin ohjelmointiympäristöihin ei ollenkaan kuulu rajanvetoa lähdekoodin kirjoittamisen, kääntämisen/tulkkaamisen ja ajamisen välillä. Yksi esimerkki tästä on tuttu Scala-REPL.

JVM:n tavukoodin tutkiminen

(Tämä vapaaehtoinen tehtävä sopinee parhaiten opiskelijoille, jotka ovat jo ohjelmoineet ennen tätä kurssia. Tehtävän aiheeseen palataan Ohjelmointi 2 -kurssilla.)

Käännetyn Scala-ohjelman sisältämiä tavukoodikäskyjä voi tutkia javap- ja scalap-nimisillä apuohjelmilla. Selvitä internetistä, miten näitä apuohjelmia käytetään, ja tutki jotakin käännettyä Scala-luokkaa niillä.

Eri virtuaalikoneita

Tutki internetin avulla, mitä muita yleisesti käytettyjä virtuaalikoneita ja välikieliä on kuin JVM tavukoodeineen. Selvitä esimerkiksi, mikä on CLR sekä miten JavaScript-kieltä nykyään käytetään muiden ohjelmointikielten välikielenä WWW-selaimissa.

Vielä biteistä, luvuista ja tietotyypeistä

Mieti, mitä käsky println(2147483647 + 1) tulostaa. Anna käsky sitten REPLissä. Vastasiko tuloste odotuksiasi?

Kirjoita tuloste tähän:

Mieti, mitä käsky println(100.0 / 11.0 * 11.0) tulostaa. Anna käsky sitten REPLissä. Vastasiko tuloste odotuksiasi?

Kirjoita tuloste tähän:

Luvun alkupäässä nousi aiheeksi se, että kaikki data, mm. luvut, esitetään tietokoneelle bitteinä. Monissa yhteyksissä datan sisäisestä binaarisesta esitysmuodosta ei kuitenkaan tarvitse välittää. Scala-ohjelma on melko korkean tason abstraktio tietokoneen sisäisestä toiminnasta, ja usein tällainen korkea abstraktiotaso riittää. Kuitenkin on tilanteita, joissa on tarpeen hahmottaa jotain Scala-ohjelmienkin toiminnasta bittitasolla. Äskeiset kaksi koodinpätkää ovat tästä esimerkkeinä. Selvitetään, mistä niiden oudoissa tulosteissa on kyse.

Bittimääristä

Luvun esittämiseen tarvittava bittimäärä voidaan laskea muuntamalla luku binaarimuotoon. Toisaalta voidaan tarkastella, montako erilaista arvoa on mahdollista kuvata tietyllä bittimäärällä:

  • Nollalla bitillä ei voida kuvata kuin yksi arvo. (Ja yhdellä arvolla ei voida ilmaista informaatiota; vrt. Unit luvusta 1.6.)
  • Yhdellä bitillä voidaan kuvata korkeintaan kaksi eri arvoa. Bitti riittää kuvaamaan esimerkiksi yhden totuusarvon, tai sen, onko luku 0 vai 1.
  • Kahdella bitillä voidaan kuvata korkeintaan neljä eri arvoa. Voidaan esimerkiksi sopia, että: 00=0, 01=1, 10=2 ja 11=3, tai että 00=0, 01=1, 10=-2 ja 11=-1.
  • Kolmella bitillä voidaan kuvata korkeintaan kahdeksan eri arvoa.
  • Kun bittejä on jokin määrä n, niillä voidaan kuvata korkeintaan 2n eri arvoa.

Int-arvoista

Suoritustehokkuussyistä (mm.) Scala-kielessä on määritelty, että kullekin Int-arvolle varataan aina tietty vakiomäärä bittejä — 32 kpl — joilla sen arvo varastoidaan. Esimerkiksi Int-tyyppisen muuttujan koko tietokoneen muistissa on siis aina 32 bittiä eli neljä tavua (byte); yksi tavu on kahdeksan bittiä.

32 bitistä saa 232 erilaista nollan ja ykkösen yhdistelmää. Näin ollen erilaisia mahdollisia Int-arvoja on 232 kappaletta — mielivaltaista määrää lukuja ei tällä tyypillä voi esittää.

On määritelty, että Scalassa nämä 232 arvoa kattavat kokonaisluvut välillä:

-2147483648 ... +2147483647

eli

-231 ... +231-1

Kääntäjä ei pidä huolta lukujen "mahtumisesta" tyyppeihin kuten ei virtuaalikonekaan. On ohjelmoijan vastuulla, että hän ei käytä tämän kokonaislukuvälin ulkopuolisia arvoja Int-tyypin kanssa.

Itseisarvoltaan liian suurilla luvuilla laskemisella voi olla yllättäviä seurauksia, kuten esimerkkilausekkeemme 2147483647 + 1 evaluoiminen osoitti. Kuten huomaat, ensimmäinen laskettava ei ole arvalla valittu vaan juuri se suurin luku, joka Int-tyyppiin mahtuu. Seurauksena on tuloksen "pyörähtäminen ympäri".

Aina ei suju ammattilaisiltakaan

Lukutyyppien rajat voivat näkyä sovellusten käyttäjillekin, ellei ohjelmoija tätä ehkäise. Tälle kurssille aiemmin osallistuneet opiskelijat ovat havainneet ilmiön peleissä:

2 147 483 647 sattui olemaan kultalimitti World of Warcraftissa ennen vanhaan. Sittemmin uusissa lisäosissa rahan keruu tuli niin helpoksi, että kultalimitti nostettiin.
Olen huomannut Hearthstonessa, että jos jonkin "minionin" elämät tai hyökkäysvahinko ylittää 2147483647 niin se tuhoaa kyseisen minionin. Nyt tiedän paremmin, miksi.
Hyvä toiseen suuntaan menevän errorin esimerkki on Gandhin käyttäytyminen Civilization I:ssä.

Vuonna 2014 Gangnam Style rikkoi YouTuben: "YouTuben ylläpitäjät joutuivat päivittämään koko palvelun, jotta se osaisi jatkaa laskemista. Gangnam Stylen katselulaskurin ylittämä kriittinen raja oli 32-bittinen kokonaisluku eli 2 147 483 647."

Vakavammasta tapauksesta oli kyse vuonna 1996 Cluster-avaruusaluksen räjähtäessä heti lähdön jälkeen. Englanninkielinen Wikipedia kertoo:

[The launch] ended in failure due to an error in the software design [which] caused inadequate protection from integer overflow. This resulted in the rocket veering off its flight path 37 seconds after launch, beginning to disintegrate under high aerodynamic forces, and finally self-destructing by its automated flight termination system. The failure has become known as one of the most infamous and expensive software bugs in history. The failure resulted in a loss of more than US$370 million.

Lentopelkoisten kannattaa laittaa silmät kiinni tämän kappaleen ajaksi. Vuonna 2015 nimittäin uutisoitiin, että Boeing 787 Dreamliner -lentokoneissa on ominaisuus, joka voi kytkeä koko koneesta virrat pois mikäli konetta ei "buutata" vähintään 248 päivän välein. Mitähän 248 päivää on muissa mittayksiköissä?

Joissakin yhteyksissä ohjelmoijan virheestä voi myös muodostua tietoturvakysymys.

Double-arvoista ja liukuluvuista

Kullekin Double-arvolle on Scalassa varattu kahdeksan tavua eli 64 bittiä. Näiden 64 bitin avulla kukin Double-arvo esitetään etumerkkibitin, kertoimen ja eksponentin yhdistelmänä ns. liukulukuna (floating-point number; Double on double precision floating-point number).

Liukulukuesityksen yksityiskohdat eivät ole tämän kurssin kannalta kiinnostavia; niihin voit halutessasi tutustua jonkin digitaalitekniikan alkeiskirjan tai internetistä löytyvän materiaalin avulla. Nyt tärkeintä on hahmottaa, että koska resurssit ovat rajalliset (eli bittejä on tarjolla vakiomäärä) Double-arvoa ei voi esittää muistissa mielivaltaisen tarkasti. Käytössä oleva bittimäärä rajoittaa sitä, kuinka suuria ja tarkkoja Double-arvot voivat olla. 64 bitillä saavutetaan noin viidentoista merkitsevän numeron tarkkuus.

Liukulukumuodon rajallisen tarkkuuden vuoksi Double-arvoilla laskettaessa syntyy usein pieniä "epätarkkuuksia", jollaisesta näit esimerkin, kun evaluoit lausekkeen 100.0 / 11.0 * 11.0. Osamäärää ei pystytty esittämään "loputtoman tarkasti", ja kun epätarkka osamäärä kerrottiin taas yhdellätoista, ei lopputulos ollutkaan täsmälleen sata.

Rajallista tarkkuutta havainnollistaa myös tämä esimerkki:

2.999999999999999res0: Double = 2.999999999999999
2.9999999999999999res1: Double = 3.0

Rajallisesta tarkkuudesta johtuen liukulukuarvoja ei usein ole järkevää esimerkiksi vertailla keskenään ==-operaattorilla.

Ilmiön seuraukset voivat muutenkin olla joskus hämmentävän näköisiä. Kokeillaan lähimpään kokonaislukuun pyöristävää round-metodia:

5.499999999999999.roundres2: Long = 5
5.4999999999999999.roundres3: Long = 6

Tässä siis jälkimmäinen literaali tarkoittaa itse asiassa liukulukua 5.5, joka pyöristyy ylöspäin.

Mikäs tuo Long tuossa äskeisessä REPL-esimerkissä muuten oli?

Scalan muista lukutyypeistä

Lukujen esittämiseen käytetään Scalassa tyypillisimmin Int- ja Double-tietotyyppejä. Aina ne eivät kuitenkaan ole tarkoitukseen riittäviä tai sopivimpia, joten muitakin vaihtoehtoja on tarjolla. Ohjelmoijan tulee valita sopiva tietotyyppi, johon käsiteltävät luvut "mahtuvat". Joissain tilanteissa ohjelmoija haluaa optimoida resurssien käyttöä ja käyttää Intiä ja Doublea vähäbittisempiä tietotyyppejä.

Tässä eräitä Scalan tietotyyppejä:

  • Suuria kokonaislukuja voi esittää käyttäen kahdeksantavuista tietotyyppiä Long (long integer), jonka puhti riittää 1018-suuruusluokkaan.
  • Resursseja voi säästää käyttämällä kokonaislukujen kuvaamiseen tietotyyppiä Short (short integer; 2 tavua) tai Byte (1 tavu). Liukuluvuille on vastaavasti käytettävissä tyyppi Float (floating-point number; 4 tavua). Tämä kannattaa vain silloin, kun tietää resurssioptimoinnin tarpeelliseksi kyseisessä yhteydessä.
  • Kun mikään ei riitä, voi käyttää scala.math.BigInt- ja scala.math.BigDecimal-tyyppejä, joilla voi kuvata mielivaltaisen kokoisia lukuja. Niillä ei ole vakiobittimäärää, vaan tilaa varataan muistista joustavasti kyseisen arvon perusteella. Haittapuolena on suuremman muistinkäytön lisäksi se, ettei tietokone laske näillä tyypeillä yhtä nopeasti kuin vakiobittisillä lukutyypeillä.

Tällä kurssilla sinun ei tarvitse käyttää muita lukutyyppejä kuin Intiä ja Doublea. Voit hakea muista tyypeistä lisää tietoa internetistä, jos aihe kiinnostaa.

Tehtävä: värejä lukuina

Yllä mainittiin, että kukin kuvan pikseli voidaan esittää lukuina, jotka vastaavat pikselin värisävyä.

Yksi tapa kuvata pikseli on RGB-muoto, jossa väri muodostuu punaisen (Red), vihreän (Green) ja sinisen (Blue) komponentin yhdistelmänä. Kukin komponenteista on luku. Lukujen suuruusluokka riippuu siitä, montako värisävyä on käytössä. Esimerkiksi näin:

  • Kukin pikselin kolmesta RGB-komponentista on yksi 256:sta lukuarvosta nollan ja 255:n väliltä. Komponentit kertovat "paljonko punaista", "paljonko vihreää" ja "paljonko sinistä" kyseisessä pikselissä on.
  • Tällöin kullakin pikselillä on yksi 2563:sta eli noin 16,8 miljoonasta värisävystä. Kutakin pikseliä kuvaavien lukujen tallentamiseen tarvitaan 3 * 8 bittiä eli kolme tavua muistia, koska kahdeksalla bitillä eli yhdellä tavulla voi esittää juuri 28 eli 256 eri arvoa.

Kurssin o1-kirjaston tuttu tyyppi Color mallintaa värejä juuri tuollaisessa RGB-muodossa. Asia on helppo havaita:

Tähän asti olemme käyttäneet vain vakiovärejä kuten Red ja LightBlue. Voimme myös luoda olion, joka kuvaa erikseen määräämäämme värisävyä:

val omaSavy = Color(220, 150, 220)omaSavy: Color = Color(220, 150, 220)
Parametrit (välillä 0–255) kertovat, millainen sävy on kyseessä. Esimerkissä luodaan marjapuuromainen sävy, jossa on erityisen paljon punaista ja sinistä.
Väri luodaan ilman new-sanaa. (Kyseessä on Color-kumppaniolion tehdasmetodi; luku 5.1.)

Jokaisella Color-oliolla on muuttujat red, green ja blue:

omaSavy.redres4: Int = 220
omaSavy.greenres5: Int = 150

Samat muuttujat ovat vakioväreilläkin. Tutki, millaiset arvot esimerkiksi White-, Black- ja Brown-värien komponenteilla on.

Entä paljonko punaista on värissä Brown?

Pic-olioilla on metodi pixelColor, jolla voi kysyä tietyn pikselin väriä: jokuKuva.pixelColor(x, y). Kokeile sitä.

Kuinka paljon punaista on siinä pisteessä, joka on kuvassa Pic("defense.png") kohdassa (100,100)?

Lisää väreistä

Väriolioilla voi tehdä muutakin. Lisää voit lukea dokumentaatiosta, mutta tässä pari esimerkkiä.

Väristä on helppoa tehdä vähän tummempi tai vaaleampi versio:

val vahanVaaleampi = Brown.lightervahanVaaleampi: Color = Color(174, 64, 64)
val kahdestiTummempi = Brown.darker.darkerkahdestiTummempi: Color = Color(133, 33, 33)
val testikuva = rectangle(100, 100, Brown).leftOf(rectangle(100, 100, vahanVaaleampi))
                                          .leftOf(rectangle(100, 100, kahdestiTummempi))testikuva: Pic = combined pic
testikuva.show()
../_images/pic_lighter_darker.png

Värillä on R-, G- ja B-komponenttien lisäksi myös ns. alfakanava eli läpinäkymättömyyttä (opacity) kuvaava tieto:

Blue.opacityres6: Int = 255
val lapikuultavaPunainen = Color(255, 0, 0, 100)lapikuultavaPunainen: Color = Color(255, 0, 0, 100)
rectangle(300, 100, lapikuultavaPunainen).onto(circle(200, Blue)).show()
Ellei toisin määritellä, värit ovat läpinäkymättömiä eli niiden opacity on suurin mahdollinen.
Läpinäkyvyyttä voi säädellä neljännellä komponentilla. Väri, jonka opacity on vain 100 on varsin läpikuultava. Nolla olisi tarkoittanut täysin läpinäkyvää.
../_images/pic_opacity.png

Yhteenvetoa

  • Tietokoneohjelmien käsittelemä data — mukaan lukien ohjelmat itse — esitetään tietokoneen muistissa bitteinä.
  • Scala-ohjelmakoodia ei ajeta sellaisenaan. Tyypillisimmin Scala-lähdekoodi käännetään ensin tavukoodiksi kutsutulle välikielelle. Kääntämisestä vastaa erillinen apuohjelma.
  • Tavukoodi puolestaan muunnetaan kohdekoneelle sopivaksi konekieleksi ohjelma-ajon yhteydessä. Tavukoodiohjelman ajamisesta vastaa virtuaalikoneeksi kutsuttu apuohjelma.
    • Scala-ohjelmien ajamiseen käytetään tavallisesti JVM-nimistä virtuaalikonetta, joka on alun perin luotu Java-ohjelmoijia varten.
    • Virtuaalikoneen käyttö parantaa ohjelmien siirrettävyyttä ja erilaisten komponenttien yhdisteltävyyttä.
  • Usein käytetyille lukutyypeille kuten Int ja Double on määritelty tietty bittimäärä, jolla niiden arvot kuvataan tietokoneen muistissa. Tämä asettaa tiettyjä rajoituksia näiden tyyppien käytölle.
  • Lukuun liittyviä termejä sanastosivulla: sovelluskehitin eli IDE; lähdekoodi, korkean tason kieli, konekieli, kääntäjä; välikieli, tavukoodi; virtuaalikone, JVM eli Java-virtuaalikone; Java, JavaScript, komentorivi, roskankeruu; bitti, tavu; liukuluku; RGB.

Syvälle Scala-työkalujen tai tietokoneen laitteiston sisään ei tässä menty. Tässä luvussa luotu yleiskuva syventyy jatkokurssilla CS-A1120 Ohjelmointi 2, jolla bitit viuhuvat.

P.S.

Tässä vielä yksi:

0.1 + 0.1 + 0.1res7: Double = 0.30000000000000004

Palaute

Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.

Tekijät

Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!

Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.

Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.

Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.

Tehtävien automaattisen arvioinnin ovat toteuttaneet Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.

Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.

Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.

O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.

Opetustapa, jossa käytämme O1Libraryn työkaluja (kuten Pic) yksinkertaiseen graafiseen ohjelmointiin on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.

Oppimisalusta A+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.

Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.

Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.

../_images/imho5.png
Palautusta lähetetään...