A block egy kódrészlet, amely átadható egy metódusnak. Más programozási nyelvekben ezt névtelen függvénynek vagy lambdának nevezik. Egy metódus a blockot Proc típusúvá alakíthatja, amely egy kódrészletet reprezentál.
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
A blockokat és a Procokat nagyon gyakran használják a Crystalban, például iterációhoz és visszahívásokhoz, de DSL-ek (domain-specifikus nyelvek) létrehozására is. A DSL-ek néhány példája a Kemal webes keretrendszer vagy a Spec tesztkeretrendszer (ezzel tesztelik a Crystal-megoldásaidat).
Ahhoz, hogy olyan metódust hozz létre, amely elfogad egy blockot, az utolsó argumentum elé egy és-jelet (&) kell tenned.
Ez a blockot Proccá alakítja, és hozzárendeli az argumentumhoz.
Ezután a yield használatával végrehajtod a metódusnak átadott blockot.
def my_method(&block)
1 + yield
end
Block átadására kétféle mód van: vagy a do ... end kulcsszavakkal, vagy kapcsos zárójelekkel ({}).
A kettő közti fő különbség a precedencia: a do ... end támogatja a többsoros blockokat, a kapcsos zárójel ({}) viszont nem.
my_method do
2
end
# => 3
my_method { 2 }
# => 3
A yield egy kulcsszó, amely végrehajtja a metódusnak átadott blockot.
Ez azt jelenti, hogy a block soha nem fut le, ha nem hívod meg a yield-et.
Argumentumokat is kaphat, amelyeket átad a blocknak.
Nincs korlát arra, hány argumentumot lehet átadni a blocknak.
Ha a blockban szeretnéd használni az argumentumokat, a |x, y, ...| szintaxist használod.
Itt az x, y, ... az argumentumok nevei.
def my_method(&block)
1 + yield 2
end
# With curly braces:
my_method { |x| x * 3 }
# => 7
# With do ... end:
my_method do |x|
x * 3
end
# => 7
A fenti példában a block a 2 argumentumot kapja, majd megszorozza 3-mal, és 6-ot ad vissza.
Ezután a metódus 1-et ad az eredményhez, és 7-et ad vissza.
Ha a yield-nek adott argumentumok száma több, mint amennyit a block vár, hiba keletkezik.
def my_method(&block)
1 + yield
end
my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)
A blockokat megírhatod explicit vagy implicit módon. Az implicit blockok a leggyakoribbak, és a fenti példákban ilyeneket láttál. Sőt, adhatsz a blocknak különböző típusú argumentumokat is; a fordító kikövetkezteti a típusokat, és egy uniótípust hoz létre.
def my_method(&block)
yield "a"
yield 1
end
my_method do |x|
p typeof(x)
end
# output: (String | Int32)
Az explicit blockok írásakor megadhatod az argumentumok típusait és a visszatérési típust.
Ezt az Arg1Type, Arg2Type, ... -> ResultType szintaxis teszi lehetővé.
Itt az argumentumokat vessző (,), a visszatérési típust pedig -> választja el.
def my_method(&block : Int32, Int32 -> Int32)
1 + yield(2, 3)
end
my_method { |x, y| x * y }
# => 7
Ahogy más explicit típusoknál, a fordító itt is kikényszeríti, hogy a típusok a vártnak megfelelőek legyenek. Ha a block nem egyezik a metódusnak megadott típussal, hiba keletkezik.
Van egy rövidített szintaxis az olyan blockokhoz, amelyek csak egy argumentumot vesznek át, és csak egy metódust hívnak meg rajta.
A |x| helyett a & jelet használhatod az argumentum neve előtt.
def my_method(&block)
yield "a"
end
# This is:
my_method { |x| x.upcase }
# => "A"
# Is the same as:
my_method &.upcase
# => "A"
A Proc nagyon szorosan kapcsolódik a blockokhoz.
A fő különbség az, hogy a Proc egy típus, a block pedig nem.
A Proc egy függvénymutatót reprezentál, és változóként is átadható.
Egy Proc definiálásához a ->(args) { ... } proc literál szintaxist használod.
Az argumentumokat vessző (,), a blockot { ... } választja el.
Egy Proc meghívásához a call metódust használod, és átadod neki az argumentumokat.
A paraméterek típusai kötelezőek.
A visszatérési típust a proc törzse alapján következteti ki a fordító, de explicit módon is megadható.
my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6
A blockok rögzítése egy módja annak, hogy Proc-vá alakíts egy blockot.
Ehhez a & operátort kell használnod a metódus utolsó argumentuma előtt.
Ez a blockot Proc-vá alakítja, és hozzárendeli az argumentumhoz.
def my_method(&block)
block.call
end
my_method { 1 + 2 }
# => 3
A Procok átadhatók olyan metódusoknak, amelyek blockot várnak. Ez akkor lehet hasznos, ha valamilyen feltételtől függően más-más blockot szeretnél átadni.
def my_method(&block)
yield 5
end
my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10
Egy változó deklarálásakor a : Type szintaxissal adhatod meg a változó típusát.
Procok esetén a Proc((ArgType, ...), ResultType) szintaxist használod.
Az utolsó típus mindig a visszatérési típus, a többi pedig az argumentumok típusa.
Ez azt jelenti, hogy ha csak egy típus van, az a visszatérési típus lesz, és a proc nem vesz át argumentumot.
my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }
Egy metódusból is deklarálhatsz procot.
def my_method(a, b)
a + b
end
my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)