A Ruby programozási nyelv

Alprogramok, modulok

1. Szintaxis

A Ruby metódusai függvények abban az értelemben, hogy mindenképp van visszatérési értékük - ez vagy a return-nel explicite visszaadott érték, vagy az utoljára kiértékelt kifejezés értéke - még ha az nil is, mint az alábbi példában:

def foo print "foo\n" end irb(main):008:0> foo foo => nil

A return-t is használhatjuk a visszatérési értékek megadására:
def foo(i) return i*2; end foo(5) => 10

Érdekes lehetőség a nyelvben, hogy alias-t definiálhatunk egy metódusra, azaz az alias megadása után a másik néven is hivatkozhatunk a metódusra. Ez azonban nem a hagyományos értelemben vett álnév képzés, hiszen ha az eredeti metódus változik, akkor a másik metódus, ami az eredeti aliasa, változatlan marad. Tehát itt inkább klónozásról van szó. Az undef segítségével megszüntethetjük a metódus definícióját, azaz innentől kedve nem létezik az adott metódus.
def foo put "foo" end undef foo foo

Eredmény:
NameError: undefined local variable or method `foo' for main:Object [...]

A defined? metódus nil-t ad vissza ha az adott metódus nem definiált, ellenkező esetben pedig a metódust leíró stringet.
def foo end defined? foo => "method" defined? foobar => nil

Megadhatunk globálisan, vagy osztály/objektumszinten egy method_missing(name, *args) szignatúrájú metódust is. Ez lefut minden olyan esetben, amikor egy nem létező metódust próbálunk meghívni azon a szinten, ahol definiáltuk.

Modulok segítségével kapcsolhatunk össze metódusokat, osztályokat és konstansokat. Két fontos előnye lehet a modulok használatának: A modulok egyben névterek is. Azaz ha például van két azonos nevű metódusunk két különböző modulban, akkor azok nevei nem fognak ütközni, mivel mindkettő az adott modul névterében van. Nézzünk egy ilyen példát.
module Trig PI = 3.141592654 def Trig.sin(x) # ... end def Trig.cos(x) # ... end end module Action VERY_BAD = 0 BAD = 1 def Action.sin(badness) # ... end end És a program, amely használja ezt a két modult: require "trig" require "action" y = Trig.sin(Trig::PI/4) wrongdoing = Action.sin(Action::VERY_BAD)

Az osztályok metódusaihoz hasonlóan itt is a modul neve és egy pont előzi meg a meghívni kívánt modulbeli metódus nevét. Modulbeli konstanst a modul neve és két kettőspont előz meg.
Mixin:

Mint ahogy már említettük, a többszörös öröklődés helyett a mixineket használhatjuk. A modulokból nem hozhatók létre példányok, példánymetódusaik azonban lehetnek. Ennek azért van értelme, mert egy osztálydefinícióban include-olhatunk egy modult. Ebben az esetben a modul példánymetódusai egyben az osztály metódusai is lesznek. A fentiek szemléltetésére álljon itt egy példa:
module Debug def whoAmI? "#{self.type.name} (\##{self.id}): #{self.to_s}" end end class Phonograph include Debug # ... end class EightTrack include Debug # ... end ph = Phonograph.new("West End Blues") et = EightTrack.new("Surrealistic Pillow") ph.whoAmI? ť "Phonograph (#537766170): West End Blues" et.whoAmI? ť "EightTrack (#537765860): Surrealistic Pillow"

A Debug modul "include"-olásával a Phonograph és az EightTrack osztály számára is elérhetővé válik a whoAmi? példánymetódus. (Megjegyzés: a Ruby-include csak nevében hasonló a C include-jához. Itt nem file-ok beemeléséről van szó, hanem egy hivatkozásról, ami a megadott modulra mutat. Ezt azért fontos megjegyezni, mert így ha - akár futás közben - változtatunk a modulon, akkor az összes osztályban, ahol "include"-oltuk látszani fog a változás.) A mixint használhatjuk pl. a Java interface-eihez hasonlóan is. Például a standard Ruby Comparable modulját "include"-oljuk az osztályunkban és megvalósítjuk a <=> összehasonlító operátort.

2. Paraméterátadás

Rubyban a paraméterátadás csak érték szerinti, referencia és cím szerint nem tudunk paramétert átadni. A paramétereknek nem kellenek a zárójelek:

def write x = "default value of x", y puts x puts y end
Változó paraméterszámú függvények definiálására az alábbi példában látható:
def f( a, b, *rest ) # ... end

A függvények paramétereinek nincs explicit típusa. Honnan tudja előre az interpreter, hogy milyen függvényeket lehet majd meghívni rá, amikor átadjuk neki az aktuális paramétert? A válasz az, hogy sehonnan. Az, hogy a paraméternek milyen függvényei vannak, akkor dől el, amikor átadódik. Ez teljesen más filozófia, mint amit C++-ban megszokhattunk. C++-ban előre meg kell mondanunk a fordítónak, hogy egy paraméternek mi lesz a típusa, amikor átadódik. Innen tudja a fordító, hogy milyen függvényeket lehet meghívni rá. Más típusú paramétert nem adhatunk a függvénynek, még akkor sem, ha volna olyan nevű függvénye. Az Ruby-interpreter viszont a hívás idejében vizsgálja meg, hogy létezik-e a megfelelő nevű és paraméterszámú függvény, és ha létezik, akkor engedi meghívni. A C++-ban típus szerinti, a Rubyban viszont név szerinti ellenőrzés történik. Az elnevezés James Whitcomb Riley "kacsatesztjére" utal, mely szerint: "Ami úgy jár, mint egy kacsa, és úgy hápog, mint egy kacsa, az egy kacsa." Tehát két azonos interfészű objektum egymással helyettesíthető, még akkor is, ha nem a közös interfész alapján hivatkozunk rájuk. Az alábbi kódrészlet szemlélteti a jelenséget. class X def print puts "X.a" end end class Y def print puts "Y.a" end end def print( object ) object.print end print( X.new ) print( Y.new ) Mindkét osztálynak van print nevű függvénye. Az X és Y osztályoknak van ugyan közös ősosztálya (pl. az Object), de ebben nincs benne a print, vagyis a közös interfészük. A két print függvénynek csak a neve és a paraméterszáma azonos, más közös tulajdonságuk nincs. Ennek ellenére mindkét osztály objektumát át lehet adni az osztályokon kívüli printnek, mert a futáshoz elég a névegyezés. Ugyanennek a kódnak a C++-beli megfelelője le sem fordulna, mert nem tudunk olyan ősosztályt mondani, amelyben benne van az a függvény.

3. Túlterhelés

A Rubyban nincs függvénytúlterhelés, ennek következménye, hogy egy osztálynak csak egy konstruktora lehet.