Jak porozumět blokům, Procesům a metodám
Úvod
Ruby poskytuje programátorovi sadou velmi silných nástrojů propůjčených z domény funkčně orientovaného programování, jmenovitě uzavření, funkce vyššího řádu a prvotřídní funkce [1]. Tyto rysy jsou implementované v Ruby přes kód bloků, Proc objektů a metod (které jsou také objekty) - koncepty které jsou blízce příbuzné se ještě liší se v subtilních cestách. Ve skutečnosti jsem zjistil, že jsem docela zmatený tímto tématem, má potíž byla v tom, abych porozuměl rozdílu mezi bloky, procesy a metodami a jejich nejlepší použití v praxi.
V tomto článku jsem zformuloval moje porozumění tohoto aspektu Ruby, které jde následkem rozsáhlého studia Ruby knih, dokumentace a komp.jaz.ruby.
Procesy
Po nekonečném hledáné v Ruby dokumentaci, Procesy jsou definovány tímto způsobem: Proces objekty jsou bloky kódu který byl vázaný na sadu lokálních proměnných. Kód může být přivolaný v různých souvislosti a ještě zpřístupní dané proměnné.
A užitečný příklad je:
def gen_times(faktor)
return Proc.new {|n| n*faktor }
end
times3 = gen_times(3) # 'faktor' je nahrazen 3
times5 = gen_times(5)
times3.call(12) #=> 36
times5.call(5) #=> 25
times3.call(times5.call(4)) #=> 60
Proces hraje v Ruby roli funkcí. Přesnější je nazývat je objekty funkce, stejně jako všechno v Ruby jsou také objekty. Takové objekty mají jméno ve folkloru - funktoru. Funktor je definovaný jako objekt, který je volán nebo nazván jako kdyby to bylo obyčejná funkce, obvykle se stejnou syntaxí, to je přesně to, co znamená Proces.
Z příkladu a definice nahoře, je zřejmé že Ruby Proces může také sloužit jako uzavření. Na Wikipedia,je uzavření definované jako funkce, která se odkazuje na nezávisle proměnné v jejich podobě zápisu. Všimněte si jak těsně jsou zmapovány v Rubyově definičním bloku kódu který byl vázaný na sadu lokální proměnných.
Více na Procesech
Proces v Ruby je prvotřídní objekt, mohou být vytvořeny během runtime, uloženy v datových strukturách, předány jako argumenty dalším funkcím a vrácené jako hodnoty dalších funkcí. Ve skutečnosti, gen_times příklad demonstruje tyto všechny kritéria, s výjimkou "předání argumentů dalším funkcím". Toto může být realizováno tímto způsobem:
def foo (a, b)
a.call(b)
end
putser = Proc.new {|x| puts x}
foo(putser, 34)
Tady je také těsnopisný zápis pro vytvoření Procesu - Kernel metoda lambda [2] (probereme si podrobněji metody již brzy, ale pro tento krát budeme předpokládat, že Kernel metoda je něco příbuzného s globální funkcí, která může být volána odkudkoli z kódu). S použitím lambda Procesu předchozí příklad může být přepsán takto:
putser = lambda {|x| puts x}
Rozdíly mezi lambda a Proc.new
Ve skutečnosti jsou dva nepatrné rozdíly mezi lambda a Proc.new. První, argument který se kontroluje. Ruby dokumentace pro lambda říká:Rovnocenná s Proc.new, s výjimkou kontrolou výsledných Proc objektů počtem parametrů prošlých když ja volána.. Pro lepší představu se to dá demonstrovat takto:
lamb = lambda {|x, y| puts x + y}
pnew = Proc.new {|x, y| puts x + y}
# funguje dobre, tiskne 6
pnew.call(2, 4, 11)
# vyhodi nam to error hlasku
lamb.call(2, 4, 11)
Druhý, je rozdíl ve způsobu, jak josu ovládané návraty z Procesů. A návrat z Proc.new návraty z ohraničující metody (chovajíci se právě jako návrat z bloku, více na toto téma později):
def try_ret_procnew
ret = Proc.new { return "Baaaf" }
ret.call
"Toto neni definovano"
end
# vytiskne "Baaaf"
puts try_ret_procnew
Zatímco návrat z lambda jedná více konvenčně, vracejíc se ke své metodě call:
def try_ret_lambda
ret = lambda { return "Baaaf" }
ret.call
"Toto se tiskne"
end
# vytiskne "Toto se tiskne"
puts try_ret_lambda
S tomto světle, bych osobně doporučoval používání lambda namísto Proc.new, jestliže není pozdější chování striktně požadované.
Ruby lambda je neobvyklá v tom že volba jmen parametru ovlivní další chování:
x = 3
lambda{|x| "x stale odkazuje na vnejsi promennou"}.call(4)
puts x # x je nyni 4, ne 3