1.4.1.10. fejezet, Blokkok, Eljárások és Lambdák
Mi is az a blokk a Ruby-ban. Erre egy példa:
3.times do |i| puts( i ) end
Ez egyenértékű az alábbival:
3.times { |i| # névtelen, u.n. Lambda függvény puts( i ) }
és az alábbival is:
3.times do |i| puts( i ) end
Nézzük meg egy tömbön keresztül a cikluskezelést:
arr = ['one','two','three','four'] arr.each do |s| puts(s) end
és egy számsorozaton:
(1..3).each do |i| puts(i) end
A ciklusok is sokfélék lehetnek. Nézzük például az alábbit:
i=0 loop { puts(arr[i]) i+=1 if (i == arr.length) then break end }
vagy ezeket:
b3 = [1,2,3].collect{|x| x*2} b4 = ["hello","good day","how do you do"].collect{|x| x.capitalize } b5 = ["hello","good day","how do you do"].each{|x| x.capitalize }
Mint látszik, a blokkok jelölése igen sokféle. de a b5-nek az értéke változatlan marad. De ha használjuk a ! jelet, megváltoztathatjuk az eredeti objektumot, és így már a kimeneti eredményt is:
b6 = ["hello","good day","how do you do"].each{|x| x.capitalize! }
Így lesz a b6 értéke:
["Hello", "Good day", "How do you do"]
És így fűzi egymás után a nagybetűvé alakított szöveget:
a = "hello world".split(//).each{ |x| newstr << x.capitalize }
Vigyázzunk azonban itt a felkiáltójel használatára. A
capitalize!
szóköz esetén (vagy ha nem történik értékváltozás) nil értéket próbál hozzáfűzni a newstr-hez, ami kivételt okozhat (TypeError). Ezt megkerülhetjük az each_byte metódussal:
a = "hello world".each_byte{|x| newstr << (x.chr).capitalize }
Az each_byte az ASCII karakterláncot bájtokra bontja, amit a chr metódussal visszaalakítunk karakterekké. Ez persze az UTF-8 karaktereknél nem működik, csak mintaként szolgált a két metódus használatára.
Hátul tesztelő ciklusra egy példát már láthattunk. A B osztály őseit felsoroló ciklus:
x=B begin x = x.superclass puts(x) end until x == nil
Eljárások és lambdák
A Ruby-ban minden objektum. Minden objektum osztályból keletkezik. Próbáljuk ki ezt egy Hash táblán:
puts({1=>2}.class)
Ha ezt egy blokkal próbálnánk ki, hibaüzenetet kapnánk.
puts({|i| puts(i)}.class)
Mégis megpróbálhatjuk objektumokká alakítani a blokkokat. Erre való a proc és a lambda függvény.
a = Proc.new{|x| x = x*10; puts(x) } b = lambda{|x| x = x*10; puts(x) } c = proc{|x| x.capitalize! }
Az "a" függvényt az a.call(100) kifejezéssel hívhatjuk. A "b" és "c" függvényeket ugyanígy. A Proc osztály annyiban tér el a lambda és proc függvényektől (Kernel metódusoktól), hogy olyan lambda objektumot hoz létre, ami nem ellenőrzi a metódus paramétereinek a számát. Például:
a = Proc.new{|x,y,z| x = y*z; puts(x) } a.call(2,5,10,100) # nincs hibajelzés b = lambda{|x,y,z| x = y*z; puts(x) } b.call(2,5,10,100) # hibás paraméterezés (ArgumentError) c = proc{|x,y,z| x = y*z; puts(x) } c.call(2,5,10,100) # hibás paraméterezés (ArgumentError)
Yield
Most nézzünk egy-két mintát a név nélküli blokkok futtatására. Ezt a yield kulcsszóval érjük el.
def caps( anarg ) yield( anarg ) end caps( "a lowercase string" ){ |x| x.capitalize! ; puts( x ) }
Itt egy blokk és egy argumentum átadás látható. A névtelen blokk átadása a paraméter lista után történik. Amikor a caps metódus hívja a yield( anarg ) metódust, a string argumentum ("a lowercase string") átadódik a blokkban, az x változó értékül kapja, nagybetűssé alakítja, majd a puts kiírja a képernyőre.
Blokk a blokkban
Már láttuk, hogyan használjunk egy blokkot, végigléptetve azt egy tömb elemein. A következőkben egy blokkot arra használok, hogy végiglépkedjen egy stringeket tartalmazó tömb elemein, mindegyiket hozzárendelve a metódus s paraméterének. A másik blokk pedig átadja a caps metódusnak sorban, hogy kezdőbetűit nagybetűssé alakítsa
["hello","good day","how do you do"].each{ |s| caps( s ){ |x| x.capitalize! puts( x ) } }
Az eredmény pedig:
Hello Good day How do you do
Eddig láttunk már eljárás hívást call metóduson keresztül, yield kulcsszóval, most nézzünk meg mégegy formáját. Ha egy metódus argumentumlistája végén & jelet teszünk a változónév elé, akkor az biztosan Proc objektumként kerül átadásra.
def abc( a, b, c ) uts('---abc---') a.call b.call c.call yield end def abc2( &d ) puts('---abc2---') d.call end abc2{ puts "four" } def abc3( a, b, c, &d) puts('---abc3---') a.call b.call c.call d.call(1) yield(2) end a = lambda{ puts "one" } b = lambda{ puts "two" } c = proc{ puts "three" } myproc = proc{|x| puts("#{x} my proc") } abc(a, b, c ){ puts "four" } abc3(a, b, c, &myproc )
Visszatérési értékek:
---abc2--- four ---abc--- one two three four ---abc3--- one two three 1 my proc 2 my proc
Az abc2 hívásánál vegyük észre, hogy a puts "four" egy névtelen blokk (puts("four")), és az abc metódus hasonlít az abc3-hoz, csak ott névtelen blokkot adunk át.
A blokk paraméterek (|abc|) csak a blokkon belül érhetők el. De az 1.8-as Ruby-ban a blokk hívási környezetébe inicializálhat helyi változót, ha a nevük egyezik (a,b,c eljárás változók). Erre érdemes odafigyelni, mert nagy galibát okozhatnak. Pl.:
a = "hello world" def foo yield 100 end puts( a ) foo{ |a| puts( a ) } puts( a ) #< a Ruby 1.8-ban ez mostmár 100. 1.9.1-ben marad "hello world".
Precedencia szabályok
A kapcsos zárójelek precedenciája erősebb, mint a do és az end.
foo bar do |s| puts( s ) end foo bar{ |s| puts(s) }
Itt a foo és a bar két metódus. Melyiknek lessz a blokk átadva? Itt a do és end közé zárt blokk a bal oldalibb metódusnak lesz átadva, vagyis foo metódusnak, míg a kapcsos zárójelek közé zárt blokk a jobb oldalibb metódusnak, a bar metódusnak adódnak át. Az alábbi metódus megegyezik a fenti első metódussal:
foo( bar ) do |s| puts( s ) end
és a következő a második metódussal:
foo( bar{ |s| puts(s) } )
Blokkok mint léptetők
Objektumok, mint a tömb vagy a Finxnum, vannak létető metódusai (pl.: 3.times()). Ha szeretnénk saját metódusokat készíteni erre a célra, a következő mintát ajánlom:
def timesRepeat( aNum ) for i in 1..aNum do yield i end end timesRepeat( 3 ){ |i| puts("[#{i}] hello world") }
Mindenképpen előnyös, objektum-orientált szemszögből, hogy egy osztálynak saját léptető metódusa legyen.
Blokk visszatérési értéke egy újabb blokk
def calcTax( taxRate ) return lambda{ |subtotal| subtotal * taxRate } end salesTax = calcTax( 0.10 ) vat = calcTax( 0.175 ) print( "Tax due on book = ") print( salesTax.call( 10 ) ) #<= prints: 1.0 print( "\nVat due on DVD = ") print( vat.call( 10 ) ) #<= prints: 1.75
Így gyárthatók metódus sablonok is.
Blokk és példány változók
Először lokális változón vizsgáljuk meg a működést.
def aFunc( aClosure ) @hello = "hello world" puts("inside the aFunc function") aClosure.call end
Ez a névtelen függvény hivatkozik a main példány @hello tulajdonságra (definiáltuk az aFunc metódusban).
aClos = lambda{ @hello << " yikes!" puts("in #{self} object of class #{self.class}, @hello = #{@hello}") } # aClos.call #<= Ez hibát jelezne, mivel a @hello még ismeretlen (nil) aFunc(aClos) #<= @hello = "hello world yikes!" puts("outside the aFunc function") aClos.call #<= @hello = "hello world yikes! yikes!" aClos.call #<= @hello = “hello world yikes! yikes! yikes!”
Eredmény:
inside the aFunc function in main object of class Object, @hello = hello world yikes! outside the aFunc function in main object of class Object, @hello = hello world yikes! yikes! in main object of class Object, @hello = hello world yikes! yikes! yikes!
Most nézzük meg, mi történik, ha ezt a függvényt egy másik objektumnak adjuk át, és hogyan módosítja a példány tulajdonságát.
aClos = lambda{ @hello << " yikes!" puts("in #{self} object of class #{self.class}, @hello = #{@hello}") } class X def y( b ) @hello = "I say, I say, I say!!!" puts( " [In X.y]" ) puts("in #{self} object of class #{self.class}, @hello = #{@hello}") puts( " [In X.y] when block is called..." ) b.call end end x = X.new x.y( aClos )
Eredménye:
[In X.y] in #<X:0x32a6e64> object of class X, @hello = I say, I say, I say!!! [In X.y] when block is called... in `block in <main>': undefined method `<<' for nil:NilClass (NoMethodError)
Világos, hogy a blokk abban a környezetben fut le, ahol létrehoztuk. A példány változót változatlanul hagyja. Habár ezzel a névvel létezik példány tulajdonság, nem látja (nil), így hozzáfűzni sem tud, kivételt generál. Ha előbb ott lenne az aFunc definíció, akkor a main objektumnak lenne @hello tulajdonsága, és azt módosítaná.
def aFunc( aClosure ) @hello = "hello world" puts("inside the aFunc function") aClosure.call end aClos = lambda{ @hello << " yikes!" puts("in #{self} object of class #{self.class}, @hello = #{@hello}") } aFunc(aClos) class X def y( b ) @hello = "I say, I say, I say!!!" puts( " [In X.y]" ) puts("in #{self} object of class #{self.class}, @hello = #{@hello}") puts( " [In X.y] when block is called..." ) b.call # <=== watch the value of the var @hello! end end puts( "======== x.y( aClos )" ) x = X.new x.y( aClos ) puts( "======== self.inspect" ) puts self.inspect puts( "======== x.inspect" ) puts x.inspect
Eredmény:
inside the aFunc function in main object of class Object, @hello = hello world yikes! ======== x.y( aClos ) [In X.y] in #<X:0x9111f94> object of class X, @hello = I say, I say, I say!!! [In X.y] when block is called... in main object of class Object, @hello = hello world yikes! yikes! ======== self.inspect main ======== x.inspect #<X:0x9111f94 @hello="I say, I say, I say!!!">
Blokk és lokális változó
x = 3000 c1 = lambda{ |z| return z + 100 } c2 = lambda{ |y| return y + 100 } c3 = lambda{ |x| return x + 100 } puts( '=== c1 ===' ) someval=1000 someval=c1.call(someval); puts(someval) someval=c1.call(someval); puts(someval) # 2.times{ someval=c1.call(someval); puts(someval) } puts( "x=#{x}" ) puts( "=== c2 ===" ) someval=1000 someval=c2.call(someval); puts(someval) someval=c2.call(someval); puts(someval) # 2.times{ someval=c2.call(someval); puts(someval) } puts( "x=#{x}" ) puts( "=== c3 ===" ) someval=1000 someval=c3.call(someval); puts(someval) someval=c3.call(someval); puts(someval) # 2.times{ someval=c3.call(someval); puts(someval) } puts( "x=#{x}" ) print("Which variables are defined?\n") print("x=[#{defined?(x)}], z=[#{defined?(z)}]")
Eredmények Ruby 1.8.7-ben:
=== c1 === 1100 1200 x=3000 === c2 === 1100 1200 x=3000 === c3 === 1100 1200 x=1100 Which variables are defined? x=[local-variable], z=[]
Különbségként itt az 1.9.1-es visszatérési érték is, amiben már változtattak a c3 hatáskörén, az x lokális változó változatlan marad a c3-on kívül:
Eredmények Ruby 1.9.1-ben:
=== c1 === 1100 1200 x=3000 === c2 === 1100 1200 x=3000 === c3 === 1100 1200 x=3000 Which variables are defined? x=[local-variable], z=[]
A lokális változókról és láthatóságukról még egy példa:
def foo2 a = 100 for b in [1,2,3] do c = b a = b print("a=#{a}, b=#{b}, c=#{c}\n") end print("Outside block: a=#{a}, b=#{b}, c=#{b}\n") end foo2
Eredmények:
a=1, b=1, c=1 a=2, b=2, c=2 a=3, b=3, c=3 Blokkon kívül: a=3, b=3, c=3
A blokk lokális változó metódus lokális változó is egyben. Azonban mielőtt hivatkozunk rá, inicializálni kell.
- A hozzászóláshoz be kell jelentkezni