Tampere University of Technology Department of Pervasive Computing TIE-20306 Principles of Programming Languages Ruby Ryhmä 8 Juho Rintala Sami Paukku
Sisällysluettelo 1 Johdanto... 3 2 Paradigma... 3 2.1 Olio-ohjelmointi... 3 2.2 Funktionaalinen ohjelmointi... 4 3 Semantiikka... 5 4 Syntaksi... 5 5 Tyypitys... 6 6 Muistinhallinta... 6 7 Lähteet... 6
1 Johdanto Ruby on vuonna 1995 julkaistu, alunperin japanilaisen ohjelmoijan Yukihiro Matsumoton kehittämä tulkattava ohjelmointikieli [1]. Sen ominaisuuksia ovat mm. puhdas oliopohjaisuus, dynaaminen tyypitys sekä yksinkertainen syntaksi ja se on saanut vaikutteita useista ohjelmointikielistä [2]. Sen yksinkertainen, skriptikieliä muistuttava syntaksi parantaa luettavuutta ja lisää tuottavuutta, minkä takia sitä käytetäänkin esimerkiksi nopeaan prototypointiin tai web-aplikaatioihin. Merkittävin käyttökohde onkin Ruby on Rails - web-ohjelmistokehys, jota käyttävät monet suuret verkkosovellukset kuten GitHub, Twitch ja SoundCloud [3]. 2 Paradigma Rubyn kehittäjän, Yukihiro Matsumoton, ohjelmointitausta ja ideologia näkyvät selkeästi kielen ominaisuuksissa kuten käytettävissä paradigmoissa sekä semantiikassa. Suurimpia vaikuttajia ovat olleet ainakin Perl, Smalltalk, Eiffel, Ada sekä Lisp [1]. Vaikka kieli sisältääkin viitteitä useammasta ohjelmointiparadigmasta, näkyy olio-ohjelmoinnin vaikutukset selkeiten. Kielen alkuperäinen tarkoitus olikin olla aidosti olio-ohjelmointia toteuttava ja helppokäyttöinen skriptikieli [4], joka pyrkii kuitenkin luomaan balanssin funktionaalisen- ja imperatiivisen ohjelmoinnin välille [1]. 2.1 Olio-ohjelmointi Ruby tukee olio-ohjelmointiparadigman ominaisuuksia kuten datan kapselointia, periyttämistä ja polymorfismia. Toisin kuin Pythonissa, Rubyssa on erilliset nimeämiskäytännöt erityyppisille muuttujille. Esimerkiksi luokan instanssimuuttujat alkavat aina @-merkillä ja näitä ei voi käsitellä luokan metodien ulkopuolella, jolloin olio-ohjelmoinnissa keskeinen periaate, tiedon piilotus (engl. information hiding), ei jää yksin ohjelmoijan vastuulle. Ohjelmassa 1 on esimerkki luokan käyttämisestä Rubylla. 1 2 3 4 5 6 7 8 9 10 11 12 13 class Sample def initialize(num1, num2) @num1 = num1 @num2 = num2 def sum @num1 + @num2 object = Sample.new(1, 2) puts object.sum Ohjelma 1. Esimerkki luokan käyttämisestä Rubylla. Tässä ohjelmassa luodaan luokka Sample (rivi 1), josta luodaan instanssi (rivi 12). Luokan rakentajalle viedään kaksi parametria, jotka tallennetaan instanssimuuttujiin @num1 sekä @num2 (rivit 3 ja 4). Rivi 13 tulostaa näiden muuttujien summan käyttäen metodia sum (rivi 7). Ohjelman lopullinen tuloste on siis kokonaisluku 3. Olio-ohjelmointiparadigma tulee Rubyssa vahvasti esille myös siinä, että kaikki sen sisältämät rakenteet ovat lähtökohtaisesti olioita. Monista muista olio-ohjelmointikielistä poiketen, jopa primitiivit kuten kokonaisluvut ovat omia luokkiansa, joilla on siten omat metodinsa. Esimerkiksi kokonaisluvuille voidaan kutsua luokan Integer metodeita seuraavasti:
12.gcd(15) Tässä esimerkissä gcd on siis luokan Integer metodi, joka palauttaa kutsujan ja parametrin suurimman yhteisen tekijän (tässä tapauksessa luvun 3). Olio-ohjelmoinnin periaatteiden laajentaminen primitiiveihin lisää kielen joustavuutta, sillä Ruby ei rajoita kielen määrittelemien luokkien laajentamista, minkä ansiosta esimerkiksi Ohjelman 2 mukainen koodi on mahdollista. 1 2 3 4 5 6 7 class Numeric def plus(x) self.+(x) y = 1.plus(2) Ohjelma 2. Esimerkki kielen määrittelemän luokan laajentamisesta. Tässä siis laajennetaan luokkaa Numeric (jonka alaluokkia ovat mm. Integer ja Float) siten, että +-operaattorin lisäksi voidaan kahden luvun summa laskea käyttäen metodia plus. Rivin 7 jälkeen muuttujan y arvo on siis 3. 2.2 Funktionaalinen ohjelmointi Yksi funktionaalisen ohjelmoinnin tärkeimpiä piirteitä on se, että funktioit ovat ns. first-class eli niitä voidaan käsitellä kuten mitä tahansa muutakin arvoa eli niitä voidaan käyttää toisten funktioiden argumentteina ja niitä voidaan palauttaa funktioista. Ruby mahdollistaa tämän käyttämällä lambda-funktioita. add = lambda { x,y x + y } Yllä oleva ohjelma on esimerkki yksinkertaisen lambda-funktion määrittelystä Rubylla. Ruby ei kuitenkaan ole puhdas funktionaalinen ohjelmointikieli, koska se sisältää myös imperatiivisen ohjelmointiparadigman piirteitä. Funktionaalisen ohjelmoinnin toteuttaminen Rubylla on siis pääasiassa ohjelmoijan vastuulla, sillä esimerkiksi tilamuutokset ja globaalit muuttujat ovat sallittuja.
3 Semantiikka Kuten aiemmassa kappaleessa todettiin, Rubyssa kaikki tietotyypit, mukaan lukien primitiivit, ovat lähtökohtaisesti olioita. Muuttujat, joihin näitä tietotyyppejä tallennetaan, toimivat viitteinä kyseisiin olioihin. Toisin kuin esimerkiksi C++-kielessä, jossa muuttujia voidaan tarvittaessa käsitellä viitesemantiikan mukaisesti mm. antamalla funktiolle argumenttina viite alkuperäiseen muuttujaan, Rubyssa sijoitusoperaatiot ja funktioiden argumentit kopioivat alkuperäisen muuttujan arvon. Tämä arvo on kuitenkin aina viite johonkin olioon. Tätä mekanismia on helpointa avata esimerkin kautta. 1 2 3 4 5 6 7 8 def add_text(str) str << " More text." str = "Assignment." hello = "Hello, World!" add_text(hello) puts hello Ohjelma 3. Esimerkki muuttujien semantiikasta. Ojelmassa 3 luodaan rivillä 6 uusi String-olio, jonka viite tallennetaan muuttujaan hello. Tämä muuttuja viedään funktiolle add_text, joka ensin lisää parametrina tuotuun merkkijonoon tekstiä ja tämän jälkeen sijoittaa tälle uuden arvon. Rivin 8 tuloste ei kuitenkaan ole rivillä 3 sijoitettu merkkijono Assignment., vaan Hello, World! More text.. Jos Ruby käyttäisi oletuksena viitesemantiikkaa funktioiden parametreissa, korvaisi rivin 3 sijoitusoperaatio alkuperäisen muuttujan arvon. Sen sijaan, Ruby luo uuden String-olion, jonka viite tallennetaan muuttujaan str koskematta alkuperäisen muuttujan arvoon. Ohjelma 3 on myös hyvä esimerkki toisesta muuttujien käsittelyyn liittyvästä ominaisuudesta. Toisin kuin esimerkiksi Pythonissa, Rubyssa merkkijonot eivät ole muuttumattomia. Tämä mahdollistaa alkuperäisen hello-muuttujan muokkaamisen rivillä 2. Vastaava ohjelma Pythonilla toteutettuna loisi uuden olion, kun merkkijono muuttuu, jolloin lopullinen tuloste olisi muuttujan alkuperäinen arvo Hello, World!. Sen sijaan mm. lukuja ilmaisevat muuttujatyypit kuten Integer ja Float ovat Rubyssa muuttumattomia. 4 Syntaksi Rubyn syntaksi muistuttaa hyvin läheisesti Pythonin syntaksia. Luokkien ja metodien määrittelyyn käytetään avainsanoja (ohjelma 1), kun taas koodilohkot voidaan määritellä sekä avainsanojen että aaltosulkujen avulla. Ruby on merkkikokoriippuvainen ohjelmointikieli, joka kannattaa ottaa huomioon muuttujien nimissä. Lisäksi muuttujien edessä voidaan käyttää etuliitteitä, jotka vaikuttavat suoraan muuttujien näkyvyysalueisiin. Globaalit muuttujat ilmaistaan käyttämällä $-etuliitettä, instanssimuuttujilla @-etuliitettä ja luokkamuuttujilla @@-etuliitettä. Vakiomuuttujat alkavat aina isolla alkukirjaimella. Käytännön syistä Rubyssä lauseiden ja lausekkeiden välille ei muodosteta erottelua vaan kaikkia ilmaisuja kohdellaan lausekkeina. Lauseen päättymisestä ilmoitetaan yleensä rivinvaihdolla mutta myös puolipisteiden käyttäminen on mahdollista. Toisin kuin Pythonissa, sisennykset eivät vaikuta Rubyssä ohjelman toimintaan.
5 Tyypitys Ruby on dynaamisesti, mutta vahvasti tyypitetty kieli. Toisin sanoen, muuttujan tyyppi voidaan muuttaa tarvittaessa ajonaikaisesti, mutta kielen tulkki tuottaa virheilmoituksen, jos eri tietotyypeille tarkoitettuja operaatioita yrittää sekoittaa keskenään eli näissä tapauksissa ei tapahdu ns. implisiittistä tyyppimuunnosta. Esimerkiksi seuraavat koodirivit eivät tuota virhettä johtuen dynaamisesta tyypityksestä. x = 1 x = "one" Sen sijaan seuraavat koodirivit tuottavat virheilmoituksen String can't be coerced into Integer. x = 1 y = "one" x + y Ruby-tulkki ei siis pyri ymmärtämään kolmannen rivin summaoperaatiota esimerkiksi muuttamalla muuttujan x arvo merkkijonoksi vaan vaatii explisiittistä tyyppimuunnosta (esimerkiksi metodilla to_s). 6 Muistinhallinta Rubyssä muistinhallinta on tehty hyvin helpoksi ohjelmoijan näkökulmasta. Ohjelmointikielen ominaisuus on, että muistin varaaminen tapahtuu automaattisesti ja vapauttamisesta vastaa automaattinen roskienkerääjä. Ruby tallentaa tietoja oliosta kekoon. Keko koostuu lohkoista, jotka ovat tarpeeksi suuria (40 tavua) pitämään sisällään tiedot yhdestä oliosta. Muistinhallinnan tapauksessa oliot ovat yksinkertaisia tietueita, joista käytetään nimitystä RVALUE. Jossakin vaiheessa, kun objekteja on luotu tarpeeksi, vapaat lohkot loppuvat keosta. Tässä vaiheessa tarvitaan roskienkerääjää, joka käyttää keon ylläpitämiseen algoritmia merkitse ja pyyhkäise (mark-andsweep). Merkitse vaiheessa algoritmi käy jokaisen keon lohkon läpi, samalla merkiten lohkot joihin on vielä viitteitä. Tämän jälkeen seuraa pyyhkäise vaihe, jossa keko käydään uudelleen läpi. Keon lohkot, joita ei merkitty merkitse-vaiheessa, vapautetaan (eli kyseisten lohkojen objektit tuhotaan). Merkityistä lohkoista taas poistetaan merkinnät, jotta algoritmi olisi valmis seuraavaa suorituskertaa varten. Kun keko on täynnä, ja kaikki keon lohkot/oliot viittaavat johonkin, ei roskienkerääjä tällöin pysty vapauttamaan enempää tilaa. Kyseisessä tapauksessa Ruby pyytää kerneliltä lisää muistia uuden keon luomiseen, jonne tiedot uusista olioista pystytään tallentamaan. 7 Lähteet [1] https://www.ruby-lang.org/en/about/ [2] https://en.wikipedia.org/wiki/ruby_(programming_language) [3] http://rubyonrails.org/ [4] http://ruby-doc.org/docs/ruby-doc-bundle/faq/faq.html