Ruby-kielen minispesifikaatio
Ohjelmointikielten periaatteet -kurssin (kevät 2011) harjoitustyö.
Tekijät
- Mikko Kangasaho
- Henri Kinnunen
- Mikko Nieminen
- Olli Jokinen
Lisenssi

Tämä Ruby-kielen minispesifikaatio on lisensoitu Creative Commons Nimeä-Epäkaupallinen-Tarttuva 3.0 Muokkaamaton lisenssillä.
Sisällysluettelo
- 1 Ruby-kielen taustaa
- 1.1 Kielen synty ja historia
- 1.2 Ruby on tulkattava kieli
- 1.3 Käyttökohteet
- 1.4 Esimerkki Ruby-ohjelmasta
- 2 Alkiorakenne - millaisista palikoista ohjelmat rakennetaan
- 2.1 Tunnukset
- 2.2 Varatut sanat ja avainsanat
- 2.3 Literaalivakiot
- 2.4 Erottimet, sisennykset ja rivinvaihdot
- 2.5 Yleisiä tyyliseikkoja
- 2.6 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
- 3 Tunnusten näkyvyysalueet
- 3.1 Lohkorakenne
- 3.2 Sulkeumat (closure)
- 3.3 Sidonta
- 3.4 Ensimmäisen, toisen ja kolmannen luokan arvot
- 3.5 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
- 4 Kontrollin ohjaus
- 5 Perustietotyypit
- 5.1 Perustyypit
- 5.2 Perinteiset tyypit
- 5.3 Osaväli
- 5.4 Symbolit
- 5.5 Hash
- 5.6 Lukujen arvoalueet
- 5.7 Tyypitykset
- 5.8 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
- 6 Laskennan kapselointi
- 6.1 Nimetyt aliohjelmat
- 6.2 Proc-oliot
- 6.3 Metodien sulkeuma
- 6.4 Hash parametrina
- 6.5 Suoritusaikaisiin virheisiin varautumisen välineet - poikkeukset
- 6.6 Rinnakkainen laskenta
- 6.7 Tyypilliset ohjelma-arkkitehtuurit
- 6.8 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
- 7 Datan kapselointi
- 7.1 Rakenteiset tyypit
- 7.2 Viitesemantiikka
- 7.3 Oliot ja luokat
- 7.4 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
- 8 Yhteenveto
- 9 Lähteet
1 Ruby-kielen taustaa
1.1 Kielen synty ja historia
Yukihiro Matsumoton aloitti Ruby-kielen kehityksen 24.2.1993. Matsumoto ja Keiju Ishitsuka kehittelivät nimeä verkkokeskustelun aikana ennen kuin kieltä oli varsinaisesti lähdetty edes kehittämään. Lopulta Matsumoto valitsi kielen nimeksi Ruby, koska rubiini (ruby) on hänen erään kollegan syntymäkivi [1].
Yukihiro Matsumotoa pidetään Ruby-kielen isänä ja hänen kantava ajatus kielen kehittämisessä oli luoda kieli, joka yhdistäisi imperatiivisen ja funktionaalisen ohjelmoinnin hyviä puolia. Matsumoto halusi luoda skriptikielen, joka on tehokkaampi kuin Perl, mutta oliomaisempi kuin Python [1]. Ruby sai vaikutteita Matsumoton pitämistä ohjelmointikielistä kuten Perlistä, Smalltalkista, Eiffelistä, Adasta sekä Lispistä [2].
Ensimmäinen versio Ruby-kielestä julkaistiin 25.12.1995 japanilaisessa uutisryhmässä. Rubyn versio oli silloin 0.95 ja kahden päivän sisällä Rubyn julkaistiin kielestä kolme eri versiota. Julkaisu sai aikaan ensimmäisen Rubylle tarkoitetun sähköpostilistan, ruby-listin. Kieli oli jo tässä vaiheessa hyvin pitkälle kehittynyt ja se sisälsi monia nykyäänkin käytettyjä ominaisuuksia kuten erittäin vahvan oliolähtöisen rakenteen, luokat, ja niiden perinnän, mixin-luokat, iteraattorit, sulkeumat (closure), poikkeusten käsittelyn sekä roskien keruun [2].
Version 1.0 julkaistiin 25.12.1996 ja kieli tunnettiin vielä tällöin pääasiassa Japanissa. Version 1.3 julkaisun myötä, vuonna 1999, avattiin ensimmäinen englanninkielinen Ruby-kieleen liittyvä sähköpostilista, Ruby-talk. Tämä kertoi myös kielen kasvavasta kiinnostuksesta muualla maailmassa. Syyskuussa 2000 Rubystä julkaistiin ensimmäinen englannin kielellä painettu kirja Programming Ruby. Kirja julkaistiin myöhemmin vapaasti kaikkien saataville.
Vuonna 2005 Rubyn käyttö lisääntyi, kun Ruby On Rails web-kehys julkaistiin [1].
1.2 Ruby on tulkattava kieli
Ruby on tulkattava kieli. Ruby 1.9:n virallinen tulkki on Koichi's Ruby Interpreter, KRI. Se tunnetaan myös nimellä YARV (Yet Another Ruby VM). Rubylle ei ole olemassa virallista spesifikaatiota, vaan edellä mainittu standarditoteutus toimii de facto -standardina [1]. YARV on kirjoitettu C-kielellä. Se kääntää lähdekoodin ensin tavukoodiksi ja tulkkaa sitten tavukoodia [4].
1.3 Käyttökohteet
Rubyä käytetään yleisesti web-sovellusten tekoon osana Ruby on Rails -ohjelmistokehystä. Esimerkiksi osia Twitteristä on tehty Ruby on Railsillä. Rubyä voidaan käyttää myös Perlin tapaan skriptikielenä, tai sillä voidaan tehdä perinteisiä sovelluksia (desktop application).
Aktiivisia Ruby-projekteja
- RubyGems - Kirjastojen (gems) hallintaan.
- JRuby - Javan virtuaalikonetta käyttävä Ruby-toteutus. Mahdollistaa Java-kirjastojen käytön Rubyllä ja Ruby-koodin suorittamisen Javan virtuaalikoneessa.
- Ruby on Rails - Ohjelmistokehys MVC-arkkitehtuurin web-sovelluksille.
- Sinatra - Railsia kevyempi web-sovelluskehys.
- DataMapper - ORM (Object Relation Mapper). Tietokantarajapinta esimerkiksi Sinatra-ohjelmiin.
1.4 Esimerkki Ruby-ohjelmasta
Ohjelman voi ajaa tallentamalla se tiedostoon ja antamalla tiedoston nimi Ruby-tulkille parametriksi. Tietojenkäsittelytieteen laitoksen koneille on asennettu Ruby-tulkki, jolloin tiedostoon fibonacci.rb tallennettu Ruby-ohjelma ajettaisiin komennolla 'ruby fibonacci.rb'. Seuraavan Fibonaccin lukuja laskevan ohjelman alussa on kerrottu Ruby-tulkin sijainti laitoksen ympäristössä, jolloin ohjelman voi ajaa ilman eksplisiittistä Ruby-tulkin käynnistämistä.
Ohjelmia voidaan ajaa myös interaktiivisen Ruby-komentotulkin (interactive Ruby shell, IRB) avulla. Komentotulkki käynnistetään komennolla 'irb' ja kaikki tässä minispesifikaatiossa annetut ohjelmaesimerkit voidaan ajaa siinä kopioimalla ja liittämällä esimerkit tulkkiin.
Ohjelma lukee yhden syöttöluvun, laskee ensimmäisen Fibonaccin luvun, joka on syöttölukua suurempi ja tulostaa kyseisen luvun. Esimerkki ei ole välttämättä tehokkain tapa tehdä asia, mutta se esittelee joitakin Rubylle tyypillisiä käytäntöjä.
2 Alkiorakenne – millaisista palikoista ohjelmat rakennetaan
2.1 Tunnukset
Tunnus voi koostua pelkistä kirjaimista tai kirjainten, numeroiden ja alaviivojen yhdistelmistä. Tunnuksen tulee kuitenkin aina alkaa joko alaviivalla tai kirjaimella. Tunnuksilla ei ole Rubyssä pituusrajoituksia [10, s. 28--29].
Paikalliset muuttujat kirjoitetaan ilman etumerkkiä. Ilmentymä-, luokka- yms. muuttujat esitellään seuraavasti:
muuttuja Paikallinen muuttuja @muuttuja Ilmentymämuuttuja (instance variable) @@muuttuja Luokkamuuttuja (class variable) $muuttuja Yleinen muuttuja (global variable) VAKIO Vakioarvot (constants) kirjoitetaan isoilla kirjaimilla
2.2 Varatut sanat ja avainsanat
Rubyssä on määritelty seuraavat avainsanat, joita ei voi käyttää esimerkiksi muuttujien, vakioiden, metodien tai luokkien nimenä [10, s. 30]:
__FILE__ __LINE__ alias and BEGIN begin break case class def defined? do else elsif END end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield
2.3 Literaalivakiot
Kokonaislukujen literaalivakiot muodostetaan Rubyssä seuraavasti [10, s. 28]:
123456 Fixnum -543 negatiivinen Fixnum 123456789123345789 Bignum 0xaabb Hexadecimal 0377 Octal 0b001001 Binary
Kokonaisluvut tulkitaan automaattisesti luokaksi Fixnum tai Bignum riippuen vakion arvosta. Vakioilla voi olla etuliitteenä negatiivista lukua ilmaisevan miinus-merkin lisäksi heksadesimaali-, oktaali-, tai binäärilukuja ilmaisevat 0x, 0 tai 0b etuliitteet. Desimaaliluvuissa desimaaliosa erotetaan kokonaisosasta pisteellä.
Merkkijonoliteraalit kirjoitetaan käyttäen heitto- tai lainausmerkkejä: 'esimerkki yksi', "esimerkki kaksi". Erona näissä on se, että heittomerkeillä rajattujen merkkijonovakioiden kohdalla on käytössä suppeampi kontrollimerkistö.
2.4 Erottimet, sisennykset ja rivinvaihdot
Välilyönti ja tabulaattori toimivat erottimina Rubyssä ja niitä voi olla kuinka monta tahansa peräkkäin. Sisennyksillä ei ole semantiikkaa, eli ne eivät vaikuta ohjelman toimintaan millään tavalla. Lauseet päätetään joko puolipisteellä tai rivinvaihdolla. Kuitenkin, jos rivi ei päättynyt puolipisteeseen, eikä kääntäjän mielestä lauseesta muodostu järkevää kokonaisuutta rivinvaihtoon mennessä, jatketaan tulkkaamista seuraavalta riviltä [10, s. 32--33].
2.5 Yleisiä tyyliseikkoja
Rubyn kääntäjä ei ole nirso muuttujien, vakioiden eikä metodien nimien kirjoitustavoista. Yleisesti kuitenkin suositellaan noudatettavan seuraavia ulkoasukäytäntöjä [25]:
- metodien_ja_muuttujien_nimet_pienellä_ja_välilyönnit_korvattuina_alaviivoilla
- Luokkien, moduulien yms nimet JavastaTutullaSyntaksilla
- VAKIOT_ISOILLA_KIRJAIMILLA_JA_ALAVIIVOILLA
- Sisennyksenä käytetään kahta välilyöntiä
- Aksessori-metodien nimessä ei käytetä get- tai set-sanoja, vaan metodi nimetään attribuutin mukaan. HUOM! Aksessoreita ei tyypillisesti kirjoiteta, vaan käytetään Rubyn attr_accessor-metodia.
Esimerkki 3
- Totuusarvon palautettavien metodien nimessä käytetään kysymysmerkkiä eikä is-sanaa. Eli active? ei is_active.
- olio.nil? mielummin kuin olio == nil ja taulukko.empty? mielummin kuin taulukko.size == 0 (Railsissa taulukko.blank?, joka on sama kuin taulukko.nil? || taulukko.empty?)
2.6 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
Ruby on oliokieli
Ruby on täysin oliopohjainen kieli. Tämä tarkoittaa sitä, että kaikki tyypit Rubyssä ovat oliotyyppejä. Metodeja voidaan siis kutsua myös esimerkiksi numeroille [2]:
Myös boolean-tyypit true ja false sekä tyhjää olio-viitettä kuvaava nil ovat olioita:
true, false ja nil ovat singleton-olioita, eli luokasta on olemassa vain yksi ilmentymä.
Täydestä oliopohjaisuudesta on hyötyä siten, että se tuo kielen abstraktiotason hieman lähemmäs ihmisen ajatusmaailmaa. Kun kaikki tyypit Rubyssä ovat olioita niin aina tiedetään, että käytettävissä ovat tietyt metodit, joilla voidaan havainnoida tai muuttaa olion tilaa. Esimerkiksi Rubyn Object-luokasta löytyy metodit, joilla oliosta voidaan luoda esimerkiksi uusi String-tyyppinen tai Integer-tyyppinen olio.
Rubyn syntaksi helpottaa elämää
Eräs Rubyn eduista on koodin luettavuus. Rubyä on mahdollista kirjoittaa siten, että sen lukeminen on hyvin lähellä englanninkielisen tekstin lukemista. Esimerkkinä:
Eli "do something unless word includes ord". Tällainen ehtolause on helppo lukea, koska se on niin lähellä sellaista tekstiä, jota ihminen on tottunut lukemaan. Tällaisten luettavien ilmausten lisäksi Ruby tukee myös if ehtolauseita. Toisaalta Rubyn salliman syntaksin monimuotoisuus helpottaa ihmisen työtä kielen kirjoittamisessa, mutta toisaalta se taas vaikeuttaa esimerkiksi Rubyn kääntämistä välikielelle. Käännöksen sisäänlukuvaiheessa on otettava huomioon erilaiset tavat ilmaista kontrollirakenteita.
3 Tunnusten näkyvyysalueet
3.1 Lohkorakenne
Rubyssä lohkona voi olla moduuli, luokka, metodi, lause tai lauseryhmä [10, s. 35]. Lohko kirjoitetaan avainsanojen (esim. def ... end) tai välimerkkien (esim. { ... }) väliin ja sen sisältämä koodi sisennetään kahdella välilyönnillä.
Tyypillisesti, Rubyn tapauksessa, lohkosta puhuttaessa tarkoitetaan sulkeumalle välitettyä funktiota.
3.2 Sulkeumat (closure)
Sulkeuma on koodi, joka välitetään funktiolle parametrina. Tavallisesti se annetaan lohkona, joka erotetaan aaltosulkeilla tai avainsanoilla "do ... end". Tulkin kannalta erottimilla ei ole väliä, mutta yleinen tapa on kirjoittaa yksiriviset lohkot aaltosulkeiden ja useampiriviset "do ... end" väliin [10, s. 141]:
Esimerkin managers-metodi palauttaa parametrina saadusta emps-taulukosta sellaiset alkiot, joille manager?-metodi palauttaa arvon true. Kyseinen toiminnallisuus toteutetaan lohkon avulla. Emps on tässä tapauksessa kokoelma ja siten sille voi kutsua Rubyn Collections-luokan select-metodia. Select saa parametrina lohkon, jossa on määritelty parametri |e|. Eli kun select-metodi iteroi kokoelman läpi, niin select välittää jokaisen elementin parametrina saamalleen lohkolle (eli select kutsuu jokaiselle elementille lohkoa). Tämän jälkeen lohkon sisältö suoritetaan ja arvo palautetaan kutsuvalle ohjelmalle.
Print-managers-metodi toimii vastaavalla tavalla ja siinä läpikäydään managers-metodin palauttama taulukko sekä tulostetaan kaikki John-nimiset työntekijät.
Lohkojen eräs kiinnostava ominaisuus on, että lohkojen sisältä pääsee käsiksi samalla ohjelmalohko-tasolla määriteltyihin muuttujiin. Esimerkiksi:
Salary_treshold on metodin paikallinen muuttuja ja siihen päästään käsiksi samassa metodissa määritellyn lohkon sisältä.
Martin Fowler [9] määrittelee tekstissään kaksi sulkeumille tärkeää piirrettä: ensinnäkin sulkeuma on "koodilohko", joka on sidottu kutsuympäristöönsä. Toisekseen sulkeuman toteuttavat kielet toteuttavat ne yleensä siten, että niiden käyttö on syntaktisesti yksinkertaista. Tästä syystä Fowlerin mukaan sulkeumia on luonnollista käyttää ja tyypillisesti niiden avulla toteutetaan monia asioita kielissä, jotka tukevat sulkeumia.
Proc
Rubyssä on mahdollista luoda sulkeuma myös Proc.new avulla. Tämän ominaisuuden avulla ohjelmoija voi sijoittaa haluamansa sulkeuman mihin tahansa muuttujaan.
Esimerkki [9]:
Tässä määritellään aluksi metodi paid_more, joka luo uuden sulkeuman. Kyseinen uusi sulkeuma tulee suorittamaan parametrina saamalleen oliolle lauseen e.salary > amount. High_paid-muuttuja luodaan kutsumalla paid_more(150). Tämä metodikutsu sitoo arvon '150' paid_more-metodin amount-muuttujaan. Tämä on kiinnostavaa, koska tuo arvo 150 on sidottu amount-muuttujaan niin kauan kuin kyseinen high_paid-muuttuja on olemassa.
Lambda
Kolmas tapa luoda sulkeumia on lambda [10, s. 192]. Lambda on hyvin lähellä Proc-luokkaa, mutta niissä on joitakin eroja. Proc-oliot eivät ole metodeita, vaan koodin pätkiä, joita suoritetaan muun koodin välissä. Lambda on taas anonyymi metodi, jota kutsutaan [10, s. 197--200].
Lambda-lohko, kuten muutkin metodit, tarkistaa parametrien lukumäärän ja aiheuttaa poikkeuksen, jos lukumäärä on väärä. Procin tapauksessa puuttuvan parametrin arvoksi tulee nil ja ylimääräiset parametrit jätetään huomioimatta.
Seuraavassa esimerkissä args-metodissa kutsutaan parametrina saatua lohkoa parametreilla (1, 2) [10, s. 200]. Lohkolle on määritelty kolme parametria, joten esimerkistä selviää Procin ja lambdan ero poikkeustilanteessa:
Toinen ero on, että Proc ylikirjoittaa kutsutun lohkon return-metodin ja lambda ei. Kaksi seuraavaa esimerkkiä havainnollistavat tätä eroa [10, s. 197]:
Proc_return-metodin suoritus lakkaa, kun samalla tasolla määritellyn Proc-olion suoritus päättyy return-lauseeseen. Metodin paluuarvo on kyseisen Proc-olion suorituksessa palautettu arvo "Proc.new". Lambda_return-metodissa taas metodin suoritus ei pääty lambdan sisällä olevaan return-lauseeseen, vaan metodi palauttaa arvon "lambda_return method finished".
Metodin käyttö sulkeumana
Rubyssä myös tavallisia metodeita voidaan käyttää sulkeumina. Koska myös lambdat ovat metodeja, ei metodien käyttö poikkea lambdan käytöstä. Metodi-olioihin pääsee käsiksi Rubyn method-metodilla [10, s. 203].
Esimerkissä Array-luokkaan määriteltyä iterate!-metodia kutsutaan sulkeumalla, joka on määritelty square-nimisenä metodina. Metodin käyttö sulkeumana on järkevää, jos sulkeuman koodia käytetään muualla ohjelmassa (DRY, Don't repeat yourself).
3.3 Sidonta
Ruby on dynaamisesti tyypitetty kieli, eli muuttuja voi viitata kaikentyyppisiin olioihin. Muuttuja, ja sen viittaama olio, sidotaan dynaamisesti ajon aikana [1] .
Seuraavia kappaleita ja esimerkkejä lukiessa on syytä muistaa, että vaikka Rubyllä voi ohjelmoida myös proseduraalisesti, on kyseessä silti oliokieli, jossa kaikki muuttujat ja funktiot kuuluvat johonkin luokkaan. Vaikka funktiota tai muuttujaa ei määrittelisi minkään luokan sisällä, tulkitsee Ruby sen kuuluvan luokkaan Object.
Staattinen sidonta
Rubyssä on staattinen muuttujien sidonta. Paikallinen muuttuja näkyy siinä lohkossa, jossa se on määritelty. Ilmentymämuuttuja näkyy kaikkialla olion sisällä, ja globaali muuttuja näkyy ohjelman ajon aikana kaikkialla [6]. Esimerkiksi seuraava koodi ei toimi, sillä aliohjelma ei näe aliohjelman ulkopuolella määriteltyä paikallista muuttujaa, vaan Ruby olettaa sen olevan self-olion metodikutsu:
Jos aliohjelman täytyy nähdä sen ulkopuolella määritelty muuttuja, täytyy muuttujan olla joko olion ilmentymämuuttuja tai globaali muuttuja. Seuraava esimerkki toimii odotetusti:
Poikkeus näkyvyyssäännöistä: Lohkot
Sulkeumien lohkoilla on omat näkyvyyssääntönsä [10, s. 142--143]. Lohkoissa määritellyt muuttujat näkyvät vain lohkon sisällä, mutta lohkon ulkopuolella samalla tasolla lohkon kanssa määritellyt muuttujat näkyvät lohkon sisällä. Lohkot ja Proc-oliot eroavat tässä suhteessa metodeista. Jos lohkon sisällä sijoitetaan arvo muuttujaan, joka on määritelty lohkon ulkopuolella, niin uutta paikallista muuttujaa ei luoda, vaan ulkopuolella määriteltyyn muuttujaan oikeasti sijoitetaan uusi arvo. Esimerkki selventää (huom! vain Ruby 1.8 ja aikaisemmat versiot!):
Ruby 1.9:ssä lohkon sisäiset muuttujat ovat aina paikallisia. Muuttujan esittely |muuttuja| -syntaksilla luo Ruby 1.9:ssä aina uuden paikallisen muuttujan. Edellä oleva esimerkki olisi Ruby 1.9:ssä:
Jos koodin haluttaisiin toimivan Ruby 1.9:ssä kuten aikaisemmissa Rubyn versioissa, täytyy olla määrittelemättä muuttujaa |muuttuja| -syntaksilla, vaan viitata suoraan ulkopuoliseen muuttujaan:
Staattisen sidonnan poikkeuksia
Rubyssä on myös mahdollista tallentaa paikallinen sidonta Binding-olioon. Tällöin jonkin lausekkeen arvo voidaan arvioida Binding-olion luontihetkellä voimassa olleessa sidonnassa. Seuraavassa esimerkissä muuttuja a ei normaalisti olisi käytettävissä metodin foo ulkopuolella, mutta koska palautamme sen hetkistä sidontaa mallintavan Binding-olion voimme tulostaa a:n arvon metodin ulkopuolella. Metodi eval laskee annetun lausekkeen arvon annetussa sidonnassa [7]. On syytä huomata, että esimerkissä sidonta on vielä olemassa, vaikka funktiosta ollaan jo poistuttu.
3.4 Ensimmäisen, toisen ja kolmannen luokan arvot
Jos ohjelmointikielessä arvon voi sijoittaa muuttujaan, välittää parametrina ja palauttaa funktion arvona, kutsutaan arvoa kyseisessä kielessä ensimmäisen luokan (first class) arvoksi. Rubyssä kaikki arvot (kokonaisluvut, reaaliluvut, totuusarvot jne.) ovat olioita. Kaikille olioille pätee ensimmäisen luokan arvojen säännöt, joten Rubyn arvot ovat ensimmäisen luokan arvoja.
Esimerkissä Fixnum-tyyppiseen muuttujaan y asetetaan arvo 3 ja muuttuja välitetään aliohjelmalle. Aliohjelma tulostaa muuttujan arvon ja palauttaa uuden numeron, jonka tyyppi on myös Fixnum. Muuttujien arvoksi voi asettaa myös funktioita, kuten seuraavassa esimerkissä käy ilmi.
Ruby on siten myös funktioiden suhteen ensimmäisen luokan kieli.
3.5 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
Rubyyn valitut muuttujien näkyvyysalueet johtuvat siitä, että Ruby on puhdas oliokieli. Tästä etuna on kapselointi, jolloin metodit eivät vahingossa muokkaa niiden ulkopuolella määriteltyjä tietorakenteita. Ensimmäisen luokan kielenä Rubyssä voi palauttaa normaalien luokkien lisäksi myös funktioita. Tämä on funktionaaliselle kielelle varsin luonnollista, mutta virhetilanteiden selvitysten ja aloittelevan ohjelmoijan kannalta tämä luo lisähaasteita.
Haasteita tuo myös sulkeumien runsas käyttö, mihin kokenutkaan ohjelmoija ei ole välttämättä tottunut. Sulkeumia käytetään johdonmukaisesti kaikkialla Rubyssä, joten sulkeumien käytön opettelu kannattaa. Sulkeumat eivät myöskään ole Rubyn erikoisuus, vaan ne ovat mukana myös mm. Pythonissa, Perlissä ja JavaScriptissä. Toisaalta sulkeumien avulla saadaan luotua hyvin elegantteja sekä usein paljon lyhyempiä ratkaisuja, kuin kielissä joissa ei ole mahdollista käyttää sulkeumia.
4 Kontrollin ohjaus
4.1 Valinta
Rubyn kieliopissa on tyypillisesti monta tapaa ilmaista kontrollirakenteita. Alla esitellyt tavat eivät ole ainoita mahdollisia. Esimerkkejä lukiessa on syytä muistaa, että kaikki paitsi nil tai false arvioituu Rubyssä trueksi.
if
If-lause toimii Rubyssä lähes samoin kuin monissa muissakin ohjelmointikielissä. Seuraavat neljä esimerkkiä [10, s. 119] kasvattavat muuttujan x arvoa yhdellä jos se on alle 10:
Ehtolauseen voi antaa myös suoritettavan koodin lopuksi [10, s. 121]. Jos koodi on vain yhden rivin mittainen voidaan if-lause kirjoittaa ilman mitään erotinta. Selkeyden vuoksi on hyvä käyttää tätä muotoa vain jos suoritettava koodi on yhden rivin pituinen. Jos suoritettava koodi on useamman rivin mittainen täytyy koodi laittaa begin--end -lohkon tai sulkujen sisään. Seuraavat esimerkit valaisevat:
If-lause Rubyssä palauttaa myös arvon:
else
If-lauseeseen voi liittyä else-haaraa. Jos if-lauseen ehto arvioituu epätodeksi, niin else-haara suoritetaan. Else-haaran suoritettava koodi täytyy erottaa else-avainsanasta samoin kuin if-lause sitä seuraavasta koodista: rivinvaihdolla, puolipisteellä tai then-avainsanalla [10, s. 119]. Seuraavassa esimerkissä x lisätään taulukkoon data, jos taulukko data on olemassa. Jos se ei ole, niin se luodaan ja x asetetaan taulukkoon.
elsif
Jos testattavia ehtoja on monta voidaan käyttää elsif-lausetta (huom! elsif ei ole kirjoitusvirhe vaan Rubyssä varattu sana on todellakin elsif eikä elseif) [10, s. 120]:
unless
Unless on if-lauseen negaatio, eli jos annettu ehto arvioituu epätodeksi tai nil:ksi, annettu koodi suoritetaan. Unless-ehtolause ja suoritettava koodi erotetaan toisistaan samoin kuin if-lauseen tapauksessa [10, s. 122--123]. Seuraavissa esimerkeissä muuttujan x arvoa kasvatetaan yhdellä jos x ei ole suurempi kuin yhdeksän:
Ehtolauseen voi antaa myös viimeisenä aivan kuten if-lauseenkin kanssa [10, s. 122]:
Unless-valintaan voi liittyä myös else-haara [10, s. 122]:
case
Case-lauseen avulla voidaan valita monesta vaihtoehdosta yksi. Rubyssä myös case-lause palauttaa arvon, kuten seuraava esimerkki [10, s. 123] osoittaa:
Case-lauseen ehto voi sisältää myös monta ehtoa. Jos yksikin ehdoista arvioituu todeksi, niin vastaava koodi suoritetaan. Ehdot voi erottaa toisistaan pilkulla tai ||-notaatiolla [10, s. 124].
Jos case-lauseen ehdoissa toistuu sama olio vertailun vasempana osapuolena, on case-lause mahdollista lyhentää antamalla vertailtava olio case-lauseen alussa [10, s. 124]:
case-lause ja === operaattori
Case-lause käyttää vertailussa === operaattoria jos vertailtava objekti annetaan heti case avainsanan jälkeen. === operaattori toimii useimmiten kuten == operaattori, mutta joillekin luokille se on määritelty eri tavoin. Edellinen koodiesimerkki on siis ekvivalentti seuraavan esimerkin kanssa [10, s. 125]
Esimerkiksi luokalle Class === operaattori on määritelty siten, että se testaa onko oikeanpuolimmainen arvo ilmentymä samasta luokasta kuin self-olio:
Javassa ja C-sukuisissa kielissä switch-lauseessa kontrolli voi pudota casen läpi. Rubyssä kuitenkin suoritetaan aina vain yksi koodinpätkä, ja sen jälkeen kontrolli poistuu case-lauseesta.
?:-operaattori
?:-operaattori toimii kuten if-lause. Operaattorille annetaan kolme operandia ja se arvioi niistä ensimmäisen arvon. Jos arvo on mitään muuta kuin false tai nil, niin suoritetaan toinen operandi. Jos ensimmäinen operandi arvioituu falseksi tai nil-olioksi, lausekkeen arvoksi tulee kolmas operandi [10, s. 127]. Seuraava esimerkki tulostaa "Sinulle on yksi uusi viesti.", jos muuttujan n ja kokonaisluvun 1 vertailu arvioituu trueksi. Jos n on esimerkiksi merkkijono "hulabaloo", niin koodi tulostaa "Sinulle on hulabaloo uutta viestiä".
4.2 Toisto eli iteraatio
Rubyssä on kolme iteraatio-lausetta: while, until ja for. Käytännössä iterointiin käytetään kuitenkin yleensä iteraattoreita [10, s.127].
while ja until
While ja until -lauseet suorittavat annettua koodia niin kauan kuin annettu ehto on voimassa (while-lause) tai kunnes ehto tulee todeksi (until-lause) [10, s.127]. Seuraavassa esimerkissä esitellään kaksi tapaa tulostaa numerot yhdestä viiteen:
Jos suoritettava lohko sisältää vain yhden lausekkeen, voidaan while ja until kirjoittaa lauseen loppuun [10, s.128]. Seuraavassa esimerkissä sekä while- että until-lauseet tulostavat numerot ykkösestä viitoseen:
for/in
For-silmukalla voi iteroida läpi jonkin kokoelman alkiot. For-silmukka toimii vain kokoelmille, joille on määritelty iteraattorin palauttava each-metodi [10, s. 129]. Muuttuja, johon alkiot sijoitetaan, ja silmukan sisällä määritellyt muuttujat ovat käytettävissä myös silmukan ulkopuolella. Myös silmukan ulkopuolella määritellyt muuttujat ovat käytettävissä silmukan sisällä. Seuraava koodi tulostaa kaikki taulukon alkiot:
for- ja each-silmukoiden ero
Each-metodille parametrina annettava lohko kuuluu eri nimiavaruuteen. Kuitenkin, kuten lohkojen kohdalla yleensäkin, samalla tasolla sen kanssa määritellyt muuttujat ovat näkyviä [10, s.130]. Edellisessä esimerkissä siis alkio ja silmukan_sisalla_maariltelty_muuttuja eivät näy silmukan ulkopuolelle, mutta silmukan_ulkopuolella_maaritelty_muuttuja kyllä näkyy silmukan sisällä.
4.3 Iteraattorit
Rubyssä on tyypillistä käyttää iteraattoreita silmukoiden toteuttamiseen. Iteraattorit saavat parametrinaan koodilohkon, jonka ne sitten suorittavat [10, s.130]. Esimerkiksi metodit next ja each ovat iteraattoreita:
Muita hyödyllisiä iteraattori-metodeita ovat muun muassa select ja reject. Select valitsee taulukkoon kaikki arvot, joille annetun lohkon suorittaminen arvioituu todeksi, reject taas kaikki jolle annettu lohko arvioituu epätodeksi:
Numeeriset iteraattorit
Loop-metodi toimii ikuisena silmukkana. Sen suorittaminen voidaan keskeyttää esimerkiksi returnilla tai breakillä. Kokonaisluvuille on määritelty upto-iteraattori, joka suorittaa annetun lohkon kaikille luvuille kutsuttua oliota vastaavan luvun ja parametrina annetun luvun väliltä. Seuraava koodi tulostaa numerot yhdestä viiteen:
downto-metodi toimii päinvastoin. Kokonaisluvuille määritelty times-metodi kutsuu annettua lohkoa lukua vastaavan määrän.
4.4 Rekursio
Yleistä
Rekursio on tapa ratkaista ongelmia tietojenkäsittelyssä. Rekursiivinen funktio jakaa isomman ongelman pienempiin osiin ja ratkaisee aliongelman kutsumalla itseään. Kun kutsuketju on mennyt niin pitkälle, että yhtään aliongelmaa ei voida enää ratkaista, niin sen jälkeen rekursiivinen funktio palauttaa arvot kutsuketjun päästä aina rekursiivisen funktion ensimmäiseen kutsuun asti.
Rekursiivinen funktio koostuu yhdestä tai useammasta perusosasta (basecase) sekä yhdestä tai useammasta rekursiivisesta osasta (recursive case). [28]
Häntärekursio ja häntäkutsu
Häntäkutsu on funktiokutsu, joka palauttaa arvon, jonka kutsuva funktio palauttaa. [29] 'Häntä' nimitys tulee siitä, että kutsukohta sijaitsee funktion häntäpäässä, eli lopussa. Häntäkutsut ovat tärkeitä, koska ne voidaan toteuttaa kutsupinossa ilman uuden aktivaatiotietueen lisäystä. Kun suoritettavassa funktiossa edetään aliohjelman kutsuun, voidaan kutsuttavan aliohjelman aktivaatiotietue sijoittaa kutsuvan ohjelman aktivaatiotietueen "tilalle". Eli kutsupinoon ei lisätä uutta aktivaatiotietuetta. [29] Tämä ratkaisu säästää tilaa. Tätä kutsutaan myös termeillä Tail Call Elimination tai Tail Call Optimization. [29]
Häntäkutsu ei ole kielen ominaisuus vaan ennemminkin kääntäjään toteutettava ominaisuus. Tästä syystä kaikki Rubyn ajoympäristöt eivät tue automaattisesti häntäkutsuja. YARV (Yet Another Ruby Virtual machine) tukee häntäkutsuja, mutta ei automaattisesti. YARV pitää kääntää siten, että sille kerrotaan, että häntäkutsut halutaan ottaa käyttöön. Tästä syystä Rubyssa ei usein näe käytettävän häntäkutsuja, koska sen toimintaan kaikilla Ruby tulkeilla ei voi luottaa. Häntärekursio (tail-recursion) tarkoittaa rekursiivista funktiota, jonka lopussa on kutsu itseensä. [29] Rekursion sijasta Rubyssä käytetään yleisemmin iteraatiota, jollaiseksi häntärekursiokin on muutettavissa.
Puurekursio
Yllä olevassa Fibonaccin luvun laskevassa funktiossa 0 ja 1 ovat perusosat ja fib(n-1) + fib(n-2) on rekursiivinen osa. Jos n > 2 niin funktio kutsuu rekursiivisesti fib(n-1) ja fib(n-2). Tästä syntyvää rekursiota kutsutaan puurekursioksi, koska se selvästi haarautuu kahteen puuhun jo ensimmäisessä kutsuvaiheessa. Kyseinen tapa laskea Fibonaccin lukuja on kuitenkin hyvin tehoton. Sen aikavaativuus on exponentiaalinen, eli erittäin huono.
Rubyllä kirjoitettuna kyseinen rekursiivinen funktio voisi näyttää esim tältä:
5 Perustietotyypit
5.1 Perustyypit
Rubyssä ei ole alkeistyyppejä, vaan kaikki tyypit ovat oliotyyppejä.
Rubyn perustyyppejä ovat numerot, merkkijonot, säännölliset lauseet, taulukot, arvoalueet, symbolit ja silput (hash).
5.2 Perinteiset tyypit
Numero voidaan antaa erilaisin tavoin: 123, 123.4 1.23e-4, 0xffff (heksadesimaali), 0b0001 (binääri) ja 0377 (oktaali). Numeroiden tyyppi on Fixnum.
Merkkijonot annetaan lainausmerkkien tai heittomerkkien välissä. Merkkijonojen tyyppi on String.
Säännölliset lauseet annetaan /-merkkien välissä ja niiden tyyppi on Regexp.
Taulukot annetaan []-merkkien välissä alkiot pilkulla eroteltuna. Taulukoiden tyyppi on Array.
5.3 Osaväli
Rubyssä arvoalueet ovat oma perustyyppinsä, jonka luokka on Range.
Arvoalueet määritellään antamalla ala- ja yläraja, jotka erotetaan joko kahdella tai kolmella pisteellä. Kaksi pistettä tarkoittaa, että viimeinen arvo kuuluu alueeseen, kolmella pisteellä kirjoitettuna ei.
Arvot voivat olla esimerkiksi numeroita tai merkkijonoja. Arvon kuuluminen alueeseen voidaan testata ===-operaatiolla.
Range-luokka sisältää metodeja, joiden avulla sitä voidaan käyttää erilaisissa valinta- ja toistolauseissa:
5.4 Symbolit
Rubyn erikoisuus ovat symbolit, joita vastaa luokka Symbol. Symbolin nimi on samalla myös sen merkkijonoesitys.
Merkkijono-esityksen lisäksi symbolilla on numeroesitys [12]. Symbolin numeroarvo on järjestelmän määrittelemä ajonaikainen yksilöllinen arvo, joka vaihtelee suorituskertojen välillä. Symboleiden samuutta voidaan siis vertailla ajon aikana niiden numeroarvoilla, mikä parantaa suorituskykyä, koska Fixnum-tyyppisten olioiden vertailu on String-olioiden vertailua nopeampaa [12].
Symboleiden tunnus on kaksoispiste nimen edessä. Ne eivät saa mitään arvoa, eikä arvoa voida syöttää.
Muuttumattomuuden ansiosta symbolit sopivat erittäin hyvin tilanteeseen, jossa konsistentin säilyttäminen on tärkeää. Symbolit ovat vakioita (constants) kevyempi tapa välittää arvoja ja niitä on tavallista käyttää Hash-olion avain-arvoina.
5.5 Hash
Hash-oliot ovat taulukkoihin rinnastettava tietorakenne, mutta joitakin merkittäviä eroja näillä on. Hashia ei ensinnäkään indeksoida nollasta nousevilla numeroarvoilla, vaan jokaisella arvolla on myös avain. Tavallisesti avaimena käytetään symbolia.
Mikään ei toki estä käyttämästä hashia taulukkomaisesti:
Hashista haetaan arvoja kuten taulukosta:
5.6 Lukujen arvoalueet
Rubyssä pienet kokonaisluvut luvut tallennetaan Fixnum-olioihin, ja suuret Bignum-olioihin. Lukujen arvoalue riippuu toteutuksesta. Esimerkiksi Mac OS X:llä Fixnumin pienin arvo on -2^62 ja suurin arvo 2^62-1. Jos muuttujan arvo kasvaa näiden rajojen ulkopuolelle, muunnetaan muuttuja automaattisesti Bignum-olioksi. Bignum-olion arvolla ei ole minimi- eikä maksimirajoja (paitsi käytettävän tietokoneen muisti). Alla olevassa esimerkistä näkee, miten muuttujan y tyyppi vaihtuu Fixnumista Bignumiksi. Rationaaliluvuissa suurimmalla ja pienimmällä arvolla ei ole rajaa, sillä rationaaliluvun osoittaja ja nimittäjä voivat olla Bignum-olioita. Liukuluvuissa käytetään Float-olioita, jonka pienimmän ja suurimman arvon rajat riippuvat ohjelmaa suorittavan laitteiston arkkitehtuurista. Yleisesti ottaen lukujen raja-arvot riippuvat laitteistoarkkitehtuurista.
Ainoastaan Float-luokan luvuille voi tapahtua "perinteisiä" yli- ja alivuotoja, joissa olion arvo hukkuu. Ylivuodon sattuessa arvoksi tulee positiivinen tai negatiivinen ääretön. Alivuodon tapauksessa arvoksi tulee nolla. Fixnumin ylivuodon tapahtuessa olio muunnetaan automaattisesti Bignumiksi. Bignumin tapauksessa alivuodon sattuessa olio muunnetaan automaattisesti Fixnumiksi.
5.7 Tyypitykset
Dynaaminen tyypitys
Ruby on dynaamisesti tyypitetty kieli, joka tarkoittaa, että muuttujien tyyppi voi vaihtua suorituksen aikana. Muuttujille ei anneta tyyppiä määrittelyn yhteydessä, vaan tyyppi päätellään sijoituslausekkeessa [13].
Vahva tyypitys
Ruby on vahvasti tyypitetty kieli, mikä tarkoittaa, että muuttujan tyypin selvittyä muuttujaan kohdistuvat operaatiot pitää olla kyseiselle tyypille sallittuja. Jos esimerkiksi yritetään yhdistää merkkijonoa ja Fixnum-lukua, ei se onnistu ilman eksplisiittistä luvun merkkijonoksi muuttamista [13].
Toinen esimerkki yhdistää dynaamisen ja vahvan tyypityksen:
5.8 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
Tietotyypit
Arvoalueet, symbolit ja hashit nostavat Rubyn oppimiskynnystä, mutta niiden käyttäminen tehostaa kielen käyttöä. Varsinkin symbolien ja hashien käyttö metodien dynaamisten parametrien yhteydessä on erittäin tehokas menetelmä. Eräs Rubyn hieno ominaisuus on sisäänrakennettu tuki säännöllisille lauseille. Monessa kielessä säännöllisten lauseiden käsittely tapahtuu jonkin ulkoisen kirjaston kautta, mutta Rubyssä, kuten Perlissä, säännölliset lauseet ovat rakennettu kieleen sisälle.
Valinta ja iterointi
Valinta on tehty syntaktisen sokeroinnin kautta miellyttäväksi. Iteraattoreiden käyttö on tyylikäs oliomainen ratkaisu. On tyylikkäämpää pyytää oliota itse iteroimaan itsensä, kuin kirjoittaa for-lause, joka tietää olion sisäisestä toteutuksesta jotain.
Tyypitykset
Yhtenä mahdollisena haittana dynaamisesta ja vahvasta tyypityksestä on se, että osa virheistä tulee ilmi vasta suorituksen aikana. Vahvan tyypityksen etuna on kuitenkin se, että tyypille kohdistuvat operaatiot eivät aiheuta ongelmia, koska aina tarkistetaan, että operaatio on tyypille sopiva. Tällöin ei tapahdu esimerkiksi epämääräisiä poikkeustilanteita, tai muuttujat eivät saa omituisia arvoja, koska muuttujille voidaan käyttää ainoastaan saman tyyppisiä operaatioita.
Rekursio
Ruby-kieli nojaa paljon iteraattoreiden käyttöön ja rekursiota näkee paljon harvemmin. Yksi suuri syy on siinä, että häntärekursioon ei voi Rubyssä luottaa, koska sen toiminnasta ei ole varmuutta kaikilla Ruby-tulkeilla. Tästä syystä Rubyllä ei tule vastaan juurikaan häntärekursioon nojautuvia toteutuksia, vaan ennemmin suositaan iteraatiota.
6 Laskennan kapselointi
6.1 Nimetyt aliohjelmat
Rubyssä on metodeja ja Proc-olioita, jotka molemmat ovat nimettyjä koodipätkiä, jotka voivat ottaa vastaan parametrejä. Metodit liittyvät aina johonkin olioon, mutta Proc-oliot eivät. Tästä syystä Proc-olioita voi kutsua funktioiksi[11].
Metodit
Metodit määritellään def-avainsanalla. Ruby on puhdas oliokieli, ja jos metodi määritellään luokkien ulkopuolelle, se tulkitaan kuuluvan Object-luokkaan [11]. Metodin määrittelyn syntaksi on seuraava:
Metodin paluuarvo on viimeiseksi suoritetun rivin arvo. Halutessaan metodista voi palauttaa arvon myös return-avainsanalla. Metodeilla on vain yksi paluuarvo; jos halutaan palauttaa useampia arvoja täytyy arvot laittaa taulukkoon. Tämän voi tehdä myös käyttämällä return-avainsanaa ja erottamalla paluuarvot pilkulla, jolloin niistä muodostetaan taulukko [11].
Metodien määrittely yksittäisille olioille
Metodeja määritellään yleensä luokille, mutta metodeja voi määritellä myös yksittäisille olioille. Tällöin määrittelysyntaksiin täytyy liittää tieto siitä mihin olioon viitataan [11]
Metodeja ei voi ylikuormittaa
Ruby eroaa esimerkiksi Javasta siten, että metodeja ei voi ylikuormittaa. Ylikuormittamista vastaava toiminnallisuus saadaan Rubyssä aikaan sillä, että parametrit voivat olla mitä vain tyyppiä, ja metodin koodissa voidaan tehdä eri asioita riippuen parametrien tyypeistä. Lisäksi parametreille voi antaa oletusarvoja, eikä oletusarvon saavia parametrejä tarvitse antaa metodia kutsuttaessa. Tällöin samaa metodia voidaan kutsua eri määrillä parametrejä [11].
Parametrien oletusarvot
Parametrien oletusarvot annetaan lisäämällä parametrin perään '='-merkki ja antamalla haluttu arvo. Jos metodia kutsutaan ilman, että oletusarvoiselle parametrille annetaan arvo, arvioidaan sille alkuarvo kutsuhetkellä [11].
Parametrien määrä voi olla mielivaltainen
Metodin voi määritellä myös siten, että sille voi antaa mielivaltaisen määrän parametrejä. Tällöin metodin määrittelyssä jonkun parametrin eteen täytyy kirjoittaa *-merkki. Tällöin parametrit asetetaan taulukkoon, jonka nimi on sama kuin parametrin. Seuraava metodi laskee yhteen mielivaltaisen määrän parametrejä ja palauttaa niiden summan:
6.2 Proc-oliot
Proc-oliot ovat sulkeumia, joten niillä voi olla pääsy niiden kanssa samalla tasolla määriteltyihin paikallisiin muuttujiin, vaikka Proc-olion koodi suoritettaisiin eri nimiavaruudessa, kuin missä se luotiin. Luokalla Proc on kahdentyyppisiä ilmentymiä, proc-olioita ja lambda-olioita. Lambda-lohko, kuten muutkin metodit, tarkistaa parametrien lukumäärän ja aiheuttaa poikkeuksen, jos lukumäärä on väärä. Procin tapauksessa puuttuvan parametrin arvoksi tulee nil ja ylimääräiset parametrit jätetään huomioimatta.
Proc-olion voi tallentaa muuttujaan ja sen koodin voi suorittaa muuttujan kautta:
Myös lambda-olion voi tallentaa muuttujaan:
6.3 Metodien sulkeuma
Metodiin voidaan liittää viimeiseksi parametriksi &-merkillä alkava parametri, joka saa arvokseen metodikutsuun liittyvän sulkeuma-lohkon. &-merkillä alkavia muuttujia voi olla vain yksi ja se täytyy sijoittaa parametrilistassa viimeiseksi.
Tyypillisesti, esimerkiksi Rubyn peruskalustossa, parametrin nimenä käytetään &block, mutta mikään ei estä nimeämästä parametria toisella nimellä.
Yield
Kontrolli voidaan siirtää metodista sulkeuma-lohkoon yield-avainsanalla. Ilman sulkeumaa yield aiheuttaa poikkeuksen. Esimerkiksi edellisen kohdan esimerkki voitaisiin kirjoittaa yieldin avulla seuraavasti:
Ohjelman kontrolli siirtyy lohkolle ja palautuu metodille, kun lohko on suoritettu loppuun.
Parametrien välitys lohkolle
Lohkolle voidaan välittää metodista parametreja kutsumalla yieldiä parametreilla.
Vaikka kontrolli voi siirtyä hetkeksi sulkeumalle, on metodeilla normaali paluuarvonsa.
6.4 Hash parametrina
Hash-olioita käytetään Rubyssä parametrien välityksessä. Jos metodille halutaan antaa useita parametreja, joista kaikki eivät ole pakollisia, parametri-arvona voidaan käyttää Hash-oliota. Tällöin avaimina käytetään symboleita, jotka toimivat eräänlaisina parametrien tunnuksina.
6.5 Suoritusaikaisiin virheisiin varautumisen välineet – poikkeukset
Poikkeukset ovat olioita, jotka edustavat jotain poikkeuksellista tilannetta. Kun jokin poikkeuksellinen tilanne tapahtuu, voidaan poikkeus nostaa (raise). Kun poikkeus on nostettu, kontrolli siirtyy poikkeuksenkäsittelijälle. Poikkeuksenkäsittelijässä määritetään miten poikkeuksesta toivutaan. Jos Ruby-tulkki ei löydä sopivaa poikkeuksenkäsittelijää, ohjelma lopetetaan ja poikkeuksen viesti tulostetaan standardisyöttövirtaan.
Poikkeukset ja kontrollirakenteet – rescue, else ja ensure
Seuraavassa esimerkissä metodin alussa on "vartalo", eli koodirivejä, jotka voivat aiheuttaa poikkeuksen. Rescue-lausetta seuraa poikkeuksenkäsittelijä. Else-haara suoritetaan, jos poikkeusta ei ole nostettu, ja ensuren jälkeen tuleva koodi suoritetaan joka tapauksessa viimeisenä ennen kontrollin poistumista metodista [10, s. 164].
Rescue määrittää poikkeuksenkäsittelijän sille koodille, joka on määritelty edellisen begin, def, class tai module -avainsanan ja rescue-avainsanan välissä [10, s. 164]:
Poikkeusten nostaminen – raise
Poikkeuksia nostetaan kutsumalla metodia raise. Raise-metodille voi antaa parametreja seuraavasti:
Poikkeuskäsittelijän löytäminen
Ruby-tulkki hakee poikkeuskäsittelijää ensin siitä lohkosta, jossa poikkeuksen nostanut rivi on. Jos samassa lohkossa ei ole rescue-lausetta, niin tulkki alkaa etsimään sitä ulommasta lohkosta. Näin jatketaan kunnes rescue-lause löytyy, tai ulompia lohkoja ei enää ole. Jos poikkeuksenkäsittelijää ei löydy, ohjelman suoritus päättyy [10, s. 160].
Poikkeus poikkeuksenkäsittelijässä
Jos poikkeuksenkäsittelijässä aiheutuu poikkeus, alkuperäinen poikkeus hylätään ja Ruby-tulkki yrittää löytää uudelle poikkeukselle poikkeuksenkäsittelijän:
Poikkeushierarkia ja omien poikkeuksien luominen
Poikkeushierarkian ylimmällä tasolla on Exception-luokka. Sen suorat aliluokat Ruby 1.9:ssä ovat NoMemoryError, ScriptError, SecurityError, SignalException, SystemExit, SystemStackError ja StandardError. Suurin osa poikkeuksista on luokan StandardError aliluokkia. StandardError edustaa virhettä, joihin ohjelmoija tyypillisesti haluaa kirjoittaa poikkeuksenkäsittelijän. Muiden ylimmän tason luokkien virheet ovat systeemitason virheitä, joille ohjelmoija tyypillisesti voi tehdä joko hyvin vähän tai ei mitään [10, s. 155]. Oman poikkeusluokan voi luoda yhdellä koodirivillä:
6.6 Rinnakkainen laskenta
Ruby ei tue aitoa rinnakkaisuutta, koska Ruby-tulkissa käytetty yleinen tulkkilukko (global interpreter lock, GIL) estää usean Ruby-säikeen yhtäaikaisen suorittamisen [15]. Javan virtuaalikonetta hyödyntävässä JRuby-toteutuksessa tätä ongelmaa ei ole, vaan siinä voidaan käyttää rinnakkaisia säikeitä [15].
Tästä rajoituksesta huolimatta Rubyssä on mahdollista tehdä säikeitä, jolloin ohjelmoija voi ajatella niitä suoritettavan rinnakkain. Jos halutaan tehdä oikeasti rinnakkaisia sovelluksia, täytyy käynnistää useita prosesseja (tai käyttää JRubya).
Säikeet (threads)
Säikeiden hallintaan Rubyssä on luokka Thread, jonka luonti-metodille new annetaan koodilohkona säikeessä suoritettava koodi. Lohkossa käytetyt muuttujat näkyvät myös muille säikeille. Säikeellä voi olla myös omia paikallisia muuttujia (esimerkki kirjasta The Ruby Programming Language [24]).
Vuorottaisrutiinit (coroutines)
Rubyn versiosta 1.9 lähtien vuorottaisrutiineja on voinut luoda Fiber-luokan avulla [14]. Esimerkiksi Fibonaccin lukuja laskeva ohjelma voidaan toteuttaa seuraavalla tavalla [14]:
Avainsana tässä on yield, joka pysäyttää (suspends) rutiinin suorituksen. Kun rutiinin suoritusta jatketaan, palautuu yieldin parametrina annettu arvo. Yield käyttäytyy siis return-lauseen tapaan, mutta yieldin jälkeen rutiinin suoritus jatkuu [14].
6.7 Tyypilliset ohjelma-arkkitehtuurit
Ohjelmointi Rubyllä tapahtuu usen erilaisten ohjelmistokehysten tukemana. Alla on esitelty mm. ohjelmistokehys Ruby on Rails sekä MVC- ja REST-arkkitehtuurit.
MVC ja Ruby on Rails
Rubyä käytetään usein Ruby on Rails (RoR) -kehyksen kanssa. RoR on web-sovellusten tekoon tarkoitettu MVC (Model View Controller) -tyyppinen kehys, jonka tarkoitus on eritellä sovellusten näkymät toimintalogiikasta [18]. MVC-mallissa malli (model) vastaa tietomallia, esim tietokannan yhtä taulua. Näkymä (view) puolestaan on vastuussa kyseisen tietomallin esittämisestä käyttäjälle. Käsittelijä (controller) jää näiden kahden osan väliin, ollen ikään kuin molempia yhdistävä osa. Käsittelijässä voidaan määritellä esimerkiksi mitä tietoja mallin määrittämästä tietomallista halutaan esittää näkymässä.
Representational State Transfer (REST) -arkkitehtuuri
REST-arkkitehtuurin on kehittänyt Roy Fielding [19] [20] ja ideana siinä on käyttää URL-osoitteita kuvaamaan eri toimintoja ja toimintojen kohteita. Myös kohteiden tilojen muutokset välitetään URL-osoitteiden avulla ohjelmiston eri komponenteille. Esimerkiksi kuva-albumien hallintaan tarkoitetussa ohjelmistossa yksittäisen kuvan poisto voisi tapahtua seuraavalla HTTP-protokollan mukaisella komennolla:
REST-arkkitehtuurin toisena ideana on tehdä palvelimen ja sitä kutsuvan ohjelmiston (esimerkiksi selain) välisestä kommunikoinnista tilatonta. Kutsuva ohjelma voidaan ohjata tarvittaessa toiselle palvelimelle ilman, että eri palvelinten välillä tarvitaan tietojen ja istuntojen synkronointia. RESTin muita hyötyjä ovat mm. ohjelmistojen helppo skaalautuminen (ei synkronointitarvetta palvelimien välillä), sekä yhdenmukaiset ja helppolukuiset rajapinnat.
Rubyn ohjelmistokehyksistä mm. Ruby-on-Rails [18] ja Sinatra [21] tukevat REST-arkkitehtuuria.
Putket ja suodattimet
Yksi vahvasti esillä oleva arkkitehtuurityyppi on putket ja suodattimet [17], eli Ruby-ohjelmia ajatellen perinteiset skriptit. Putket ja suodattimet tarkoittaa sitä, että jokainen ohjelma tuottaa jotain, mitä voidaan käyttää seuraavan ohjelman syötteenä. Usein se on dataa, tavuja tai bittejä. Tämän tyyppisiä ohjelmia voidaan Unix-järjestelmissä putkittaa. Unix-järjestelmissä Ruby-skriptejä voidaan ajaa samaan tyyliin kuin Perl-skriptejä, eli skriptitiedosto voidaan määritellä suoritettavaksi ja sen jälkeen suorittaa.
Olio-ohjelmointi
Koska Ruby on täysin oliopohjainen kieli, myös oliopohjainen arkkitehtuuri sopii kielelle luontevasti. Ohjelmat koostuvat siis luokista, jotka mallintavat reaalielämän asioita. Varsinainen ohjelman toimintalogiikka kapseloidaan luokkien sisälle ja logiikkaan päästään käsiksi ainoastaan hyvin määriteltyjen rajapintojen kautta.
6.8 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
On kätevää, että metodien lisäksi voi luoda Proc-olioita, joita voi muunmuassa välittää parametreina. Tämä mahdollistaa geneerisempien metodien kirjoittamisen. Metodikutsuissa ja metodien määrittelyssä on mukavaa syntaktista sokeria, että sulkuja ei ole pakko kirjoittaa, jos ei halua.
Rinnakkaisuuden puutteen takia Ruby-kielellä toteutetut ohjelmat eivät pysty hyödyntämään useita prosessoreja, joten mahdollinen nopeushyöty jää saavuttamatta. Toisaalta, rinnakkaisuuden puuttuminen helpottaa ohjelmoijan työtä, kun lukkiutumia yms. tilanteita, joita rinnakkaisessa laskennassa tulee vastaan, ei tarvitse ottaa huomioon.
Muilla kielillä toteutettuja kirjastoja voidaan integroida Rubyyn helposti, koska esimerkiksi C:llä kirjoitetuissa kirjastoissa ei useinkaan ole otettu rinnakkaisuutta huomioon [16].
7 Datan kapselointi
7.1 Rakenteiset tyypit
Tietue (struct) on Rubyn rakenteinen tyyppi. Sitä käytetään melko samanlaisiin tarkoituksiin kuin silppuja (hash), mutta niillä on eroja [22]. Tietueissa käytetään silppujen tapaan symboleita kuvaamaan avainarvoja. Tietueisiin voidaan liittää toiminnallisuutta, mutta silppuihin ei.
Rubyn tietueet ovat kaikki olioita, mutta kieli tarjoaa välineitä luokkien helppoon määrittelyyn, jolloin ne käytännössä muistuttavat C-sukuisista kielistä tuttuja tietueita. Rubyn tietueet ovat lähempänä C++:n tietueita kuin C:n, sillä ne tarjoavat datan lisäksi myös toiminnallisuutta [22].
Tietueet
Tietuita käyttämällä voidaan luoda nopeasti luokka, joka sisältää luokkamuuttujia ja aksessoreja. Tietue määritellään Struct-luokan new-metodilla, parametrina tietueen kentät symboleina. Tästä tietueesta voi luoda ilmentymiä ja käyttää niitä normaalin olion tavoin [22]. Itse asiassa tietueet ovat tyypiltään Class, eli Struct-luokka on loppujen lopuksi vain syntaktista sokeria.
Tietueet käyttäytyvät taulukon ja silpun tavoin, tietyin rajoituksin [22].
Tietueille voi määritellä toiminnallisuutta antamalla Structin new-metodille koodilohkon, jossa määritellään halutut metodit [22]:
Luetellut tyypit
Ruby ei tarjoa lueteltuja tyyppejä, mutta silppuja, joissa avaimina käytetään symboleja, voidaan käyttää tähän tarkoitukseen [22]. Myös Struct-tyyppiä voidaan käyttää lueteltuna tyyppinä silpun tapaan.
Myös luokan vakioarvoja voidaan käyttää lueteltujen tyyppien määrittelyyn. Vakioiden hyöty verrattuna silppuihin on, että vakioita voidaan käyttää myös muista luokista. Toisaalta silpuissa on each-metodi, jota voidaan käyttää lueteltujen tyyppien iteroimiseen [22].
Edellinen esimerkki käyttäen vakioita:
7.2 Viitesemantiikka
Rubyssä käytetään olioille viitesemantiikkaa, eli muuttujat sisältävät viittauksia olioihin. Sijoituksen a=b jälkeen muuttujat a ja b viittaavat siis samaan olioon. Pienenä poikkeuksena viitesemantiikkaan ovat nil, Fixnum ja Boolean. Näidenkin kohdalla käytetään viitesemantiikkaa, mutta sama arvo kohdistuu aina samaan olioon. Boolean-muuttujan arvon ollessa false viittaa muuttuja aina olioon, jonka tunnus on 0. Boolean arvo true kohdistuu puolestaan aina olioon, jonka tunnus on 2. Vastaavasti muuttujan arvon ollessa nil kohdistuu viittaus olioon, jonka tunnus on 4.
Alla olevalla esimerkillä voidaan todentaa String-olion viitesemantiikka.
7.3 Oliot ja luokat
Oliot Rubyssä
Rubyssä kaikkea käsitellään olioina [24 s. xxiv]. Ruby on aidosti oliokieli. Se ei erittele esimerkiksi Javan tapaan tyyppejä perus- ja oliotyypeiksi. Rubyssä kaikki tyypit ovat olioita. Siten niille on käytettävissä aina samoja välineitä olion tilan tutkimiseen, koska kaikki oliot periytyvät luokasta Object. Olio muodostetaan luokasta kutsumalla sille konstruktoria esim. Human.new.
Oliolla on yksiselitteinen tunniste (Object ID). Alla olevassa esimerkissä nähdään Rubyn tapa käsitellä olioita. Rubyssä esimerkiksi uuden merkkijono-olion sijoittaminen muuttujaan luo aina uuden olion.
Oliolle voi määritellä normaaliin olio-ohjelmointityyliin omia ilmentymämuuttujia. Ilmentymämuuttujien syntaksi Rubyssä on @instance_variable.
Rubyn eräs erikoinen ominaisuus on mahdollisuus määritellä yksittäiselle oliolle omia metodeita (laskennan kapselointi). Tällöin kyseinen metodi on käytettävissä ainoastaan sillä oliolla, jolle se on määritelty.
Rubyn metodeita kutsutaan lähettämälle oliolle viesti, joka sisältää metodin nimen, sekä kaikki kyseisen metodin tarvitsemat parametrit [24 s. 36]. Kun olio saa viestin, tarkistaa se löytyykö kyseisen nimistä metodia sen omasta luokasta. Jos löytyy, metodi suoritetaan. Jos metodia ei löydy, tarkistetaan löytyykö se yliluokasta ja sen yliluokasta ja sen yliluokasta... Jos metodia ei löydy mistään, nostetaan poikkeus [25].
Luokat
Rubyn luokka on myös olio. Se on luokan Class ilmentymä. Luokka sisältää kaikki olioiden ominaisuudet, sekä listan luokalle määritellyistä metodeista sekä viittauksen luokan yliluokkaan [26].
Yllä olevassa esimerkissä on tyypillinen Ruby-luokka, jossa on määritelty konstruktori, yksi luokkametodi ja yksi ilmentymämetodi. Ruby on "kirjoitusystävällinen" tarjoamalla get- ja set-metodien luonnin attr_accessor metodilla. Tälle metodille annetaan parametreina ilmentymämuuttujien nimet symboleina. Tämän jälkeen olion tilaa voi tarkastella ja muuttaa suoraan käyttämällä attribuuttien nimiä.
Luokan näkyvyysmäärittelyt
Ruby tukee luokkien metodien näkyvyysmäärittelyjä oliokielissä tyypillisillä public, protected ja private määreillä. Public ja protected ovat monista kielistä tutut: Public näkyy kaikkialle, myös ulkopuolisille luokille, protected näkyy luokan sisälle ja luokan aliluokille. Protected näkyy myös saman luokan muille ilmentymille. Private on toteutettu Rubyssä siten, että se näkyy oliokohtaisesti vain oliolle itselleen. Ts. private määreellä varustetut metodit eivät näy toisille luokan ilmentymille, vaikka ne olisivat saman luokan ilmentymiä, kuin olio itse.
Luokkien perintä
Rubyssä luokka peritään syntaksilla: class aliLuokka < Luokka. Ruby tukee yksinkertaista luokan perintää, mutta ei luokkien moniperintää. Luokkien moniperinnän sijaan Ruby toteuttaa niin sanotut mixin-luokat [25] . Rubyssä aliluokasta on mahdollisuus päästä käsiksi yliluokan metodeihin super kutsulla. Kun aliluokasta kutsutaan super, niin tällöin kutsutaan yliluokan samannimistä metodia, kuin missä aliluokassa oltiin. Samalla yliluokasta kutsutulle metodille voidaan välittää parametreja.
Yksinkertainen esimerkki luokkien perinnästä Rubyssä.
Ruby ei tue moniperintää, vaan luokalla voi olla vain yksi yliluokka. Rajoitusta voidaan kiertää mixin-luokkien avulla.
Mixin-luokat
Mixin-luokkaa voi ajatella seuraavasti "A class can mixin a module", eli vapaasti käännettynä, luokka voi yhdistyä moduulin kanssa. Moduuli tarkoittaa Rubyssä metodikokoelmaa, jonka sisältämät metodit mixin-luokka saa käyttöönsä. Luokka ei peri näitä metodeita, vaan luokasta on yksinkertaisesti viittaus tähän kokoelmaan. Jos kokoelmaa muutetaan ohjelman suorituksen aikana, kaikkien tätä kokoelmaa käyttävien mixin-luokkien toiminta muuttuu. Moduulit otetaan luokassa käyttöön avainsanalla include.
Mixin-luokkien avulla voidaan saada käyttöön useamman moduulin metodit. Mixin-luokkien hieno ominaisuus on siinä, että se ei aiheuta moniperinnän kaltaisia ongelmia. Timantti-ongelmaa (diamond problem [5]) ei esiinny mixin-luokkien kanssa.
Timanttiongelma tarkoittaa epäselvyyttä, joka liittyy kun luokat B ja C perivät luokan A. Luokka D perii moniperintänä luokan B ja C ja molemmissa luokissa ylikirjoitetaan luokan A metodi equals. Tämän jälkeen, kun luokka D kutsuu luokassa A määriteltyä metodia equals, niin ongelma on valitaanko luokassa B vai luokassa C määritelty metodi. Rubyssä mixin-luokkaan valitaan viimeisimpänä käyttöönotetun moduulin metodi (reverse-inclusion-order depth-first).
7.4 Arvio kieleen valittujen ratkaisujen eduista ja haitoista
Struct
Struct-tyyppi nopeuttaa yksinkertaisten luokkien luomista, koska tyyppi voidaan määritellä yhdellä lauseella, eikä tarvita erillistä class-määritystä. Lisäksi Struct-luokan ilmentymällä on hyödyllisiä sisäänrakennettuja metodeja mm. kenttien iteroimiseen tai käsittelyyn.
Lueteltujen tyyppien puuttuminen
Silpuilla (hash) ja vakioilla voidaan kiertää lueteltujen tyyppien puuttuminen. Olemattomalla avaimella silpusta hakeminen ei kuitenkaan aiheuta poikkeusta, vaan palauttaa arvon nil, kun esimerkiksi Javassa Enumin käyttö virheellisellä "avaimella" aiheuttaa käännösaikaisen virheen. Vakioilla väärinkirjoitetut nimet kuitenkin aiheuttavat ajonaikaisen virheen, mutta vakioita käytettäessä menetetään joitakin silpun tuomia etuja, esimerkiksi arvojen iterointi.
Luokat ja oliot
Rubyn "kaikki ovat olioita" ajattelutapa helpottaa ohjelmoijan työtä, koska tällöin kaikkea ajatellaan olioina. Kaikelle on siis käytössä aina tietyt metodit, joilla voidaan tarkastella olion tilaa. Tällainen ratkaisu pitää kielen abstraktiotason myös perinteisempiä (esim. Java, C++) kieliä korkeampana, sillä Rubyssä ei ole mukana minkäänlaisia perustyyppejä. On ratkaisulla myös huonot puolensa. Muistinkulutus on korkeampi, koska kaikki kääritään aina olioiden sisään. Ruby ei siis ole omimmillaan sellaisissa sovelluksissa, joissa halutaan ehdotonta tehokkuutta. Rubyssä on ajateltu ohjelmoijaa kielen suunnittelussa ja karsittu tehokkuudessa, jotta kielellä olisi mahdollisimman helppo kuvata reaalimaailman ongelmia.
Rubyssä on viety private näkyvyysmääre normaaleja kieliä tiukemmaksi. Rubyn private sallii ainoastaan ilmentymän itsensä käyttää privatella määriteltyjä metodeita. Esimerkiksi Javassa private määreellä varustettua metodia voidaan käyttää myös saman luokan toisesta ilmentymästä. Rubyn ratkaisu lisää luokkien kapselointia ja on yksi keino varmistaa olioiden eheyttä.
Viitesemantiikka on helppo sisäistää jos ohjelmoijalla on tuntemusta esimerkiksi Javasta tai Pythonista. Mallissa on muun muassa se etu, että jokainen olio luodaan eksplisiittisesti ja jokaisen luonnin yhteydessä kutsutaan olion konstruktoria.
8 Yhteenveto
Ruby on nykyaikainen ja monipuolinen oliopohjainen ohjelmointikieli. Kielen syntaksi sallii ohjelman kirjoittamisen helposti luettavassa muodossa, mutta toisaalta monipuolinen syntaksi mahdollistaa saman ohjelman eri osien kirjoittamisen täysin eri tyylisesti. Tämä voi joissain tapauksissa vaikeuttaa ohjelman ymmärrettävyyttä. Ensimmäisen asteen kielenä Rubyssä voi funktioiden paluuarvoina palauttaa funktioita. Tämä on luonnollista funktionaalisten kielten taitajille, mutta uusien käyttäjien ja virheiden selvittämisen näkökulmasta funktionaalisuus ja sulkeumat tuovat kieleen paljon monimutkaisuutta.
Vaikkakin Rubyyn on sisällytetty suuri määrä piirteitä muista moderneista ohjelmointikielistä, on mielenkiintoista, että rinnakkaisuutta ei ole sallittu (rinnakkaisuuden saa käyttöön JRuby-tulkilla). Tällöin luonnollisestikin on estetty rinnakkaisuuden tuomien ongelmien esiintyminen ohjelmissa, mutta samalla sovellusten käyttökohteista rajautuvat pois kaikki rinnakkaisuudesta ja hajautuksesta hyötyvät sovellusalueet.
Rubyssä on paljon ominaisuuksia, joiden mukana olon voi tulkita joko hyväksi tai huonoksi asiaksi. Kokenut ohjelmistokehittäjä osaa käyttää Rubyn tarjoamia piirteitä tehokkaasti hyödyksi, mutta aloittelevalla ohjelmoijalla voi kielen täysipainoinen hallinta viedä paljon aikaa.
9 Lähteet
- Wikipedia, Ruby. [luettu 17.3.2011]
- Ruby-lang.org, About. [luettu 17.3.2011]
- Wikipedia, YARV. [luettu 17.3.2011]
- Sasada, K., YARV: yet another RubyVM: innovating the ruby interpreter. Companion to the 20th annual ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications, OOPSLA '05, New York, NY, USA, 2005, ACM, sivut 158--159, URL http://doi.acm.org/10.1145/1094855.1094912.
- Wikipedia, Diamond problem. [luettu 17.3.2011]
- Wikibooks, Ruby programming, syntax, variables and constants. [luettu 24.3.2011]
- Jacob Repp, Alternatives to lexical binding with Ruby. julkaistu 6.1.2008 [luettu 24.3.2011]
- Onestepback.org, Variable bindings in Ruby. julkaistu 23.12.2003 [luettu 24.3.2011]
- Martinfowler.com, Closure. julkaistu 8.9.2004 [luettu 24.3.2011]
- Flanagan, D. ja Matsumoto, Y., The Ruby Programming Language. O'Reilly, 2008.
- Robert Sosinski, Understanding Ruby blocks, Procs and Lambdas. julkaistu 21.12.2008 [luettu 31.3.2011]
- Troubleshooters.Com, Code corner and Ruby revival: The Ruby_newbie guide to symbols. [luettu 31.3.2011]
- François Lamontagne, Ruby is dynamically and strongly typed. julkaistu 9.7.2007 [luettu 31.3.2011]
- Werner Schuster, Ruby 1.9 adds Fibers for lightweight concurrency. julkaistu 24.8.2007 [luettu 8.4.2011]
- Ilya Grigorik, Concurrency is a myth in Ruby. julkaistu 13.11.2008 [luettu 8.4.2011]
- Wikipedia, Global interpreter lock. [luettu 8.4.2011]
- Wikipedia, Pipeline software. [luettu 11.4.2011]
- Ruby-on-Rails, API. [luettu 11.4.2011]
- Roy Fielding, Architectural styles and the design of network-based software architectures. , University of California, 2000 [luettu 11.4.2011]
- Wikipedia, Representational state transfer. [luettu 11.4.2011]
- Sinatrarb.com [luettu 11.4.2011]
- Robert Klemme, Practicing Ruby, Structs inside out, blogi. julkaistu 21.9.2009 [luettu 7.4.2011]
- François Lamontagne, Enumerations and Ruby. julkaistu 17.8.2007 [luettu 7.4.2011]
- Dave Thomas, Chad Fowler, Andy Hunt, Programming Ruby 1.9, The pragmatic programmers’ guide, Third edition, 2009.
- Ruby-doc.org, Classes tutorial. [luettu 21.4.2011]
- Ruby-doc.org, Classes. [luettu 21.4.2011]
- The Unofficial Ruby usage guide. [luettu 21.4.2011]
- Wikipedia, Recursion in computer science. [luettu 28.4.2011]
- Wikipedia, Tail call. [luettu 28.4.2011]