Šťastný nový rok a vítejte zpět v Ruby Magic! V tomto zimním díle se ponoříme do vazeb a rozsahů. Nasaďte si tedy lyže a následujte nás hluboko do lesů.
Posledně jsme se zabývali uzávěry v jazyce Ruby porovnáním bloků, proců a lambd. Kromě rozdílů mezi těmito třemi typy jsme se dotkli toho, co definuje uzávěr.
Uzávěr je funkce první třídy s prostředím. Prostředí je mapování na proměnné, které existovaly v okamžiku vytvoření uzávěry. Uzávěr si zachovává přístup k těmto proměnným, i když jsou definovány v jiném oboru.
Prozkoumali jsme ekvivalent funkce první třídy v jazyce Ruby, ale prostředí jsme pohodlně přeskočili. V tomto díle se podíváme na to, jak toto prostředí funguje pro uzávěry, třídy a instance tříd, a prozkoumáme, jak Ruby zachází s lexikálním oborem prostřednictvím svých vazeb.
Lexikální rozsah
V programování se oborem rozumí vazby dostupné v určité části kódu. Vazba neboli vazba jména váže jméno na odkaz v paměti, podobně jako jméno proměnné na její hodnotu. Obor definuje, co self
znamená, jaké metody lze volat a jaké proměnné jsou k dispozici.
Ruby, stejně jako většina moderních programovacích jazyků, používá statický rozsah, často nazývaný lexikální rozsah (na rozdíl od dynamického rozsahu). Aktuální obor vychází ze struktury kódu a určuje proměnné dostupné v určitých částech kódu. To znamená, že rozsah se mění, když kód přeskakuje mezi metodami, bloky a třídami – všechny mohou mít například různé lokální proměnné.
1 2 3 4 5 6 |
def bar foo = 1 foo end bar # => 1 |
V této metodě vytvoříme lokální proměnnou uvnitř metody a vypíšeme ji na konzolu. Proměnná je v oboru uvnitř metody, protože je tam vytvořena.
1 2 3 4 5 6 7 |
foo = 1 def bar foo end bar # => NameError (undefined local variable or method `foo' for main:Object) |
V tomto příkladu vytvoříme proměnnou mimo metodu. Když proměnnou zavoláme uvnitř metody, dostaneme chybu, protože proměnná je mimo obor. Lokální proměnné mají těsný rozsah, což znamená, že metoda nemůže přistupovat k proměnné mimo sebe, pokud není předána jako argument.
1 2 3 4 5 6 7 |
@foo = 1 def bar @foo end bar # => 1 |
Zatímco lokální proměnné jsou dostupné lokálně, instanční proměnné jsou dostupné pro všechny metody instance třídy.
Dědičné obory a vazby Proc
Jak jsme viděli v předchozích příkladech, obor je založen na umístění v kódu. Lokální proměnná definovaná mimo metodu není v oboru uvnitř metody, ale může být zpřístupněna tím, že se z ní stane proměnná instance. Metody nemohou přistupovat k lokálním proměnným definovaným mimo ně, protože metody mají svůj vlastní obor s vlastními vazbami.
Procesy (včetně bloků a lambda, v důsledku) jsou jiné. Kdykoli je proc instancován, vytvoří se vazba, která dědí odkazy na lokální proměnné v kontextu, v němž byl blok vytvořen.
1 2 |
foo = 1 Proc.new { foo }.call # => 1 |
V tomto příkladu nastavíme proměnnou s názvem foo
na 1
. Interně objekt Proc vytvořený na druhém řádku vytvoří novou vazbu. Při volání proc se můžeme zeptat na hodnotu proměnné.
Protože se vazba vytváří při inicializaci proc, nemůžeme vytvořit proc před definováním proměnné, i když je blok volán až po definování proměnné.
1 2 3 |
proc = Proc.new { foo } foo = 1 proc.call # => NameError (undefined local variable or method `foo' for main:Object) |
Volání proc vytvoří NameError
, protože proměnná není definována ve vazbách proc. Veškeré proměnné, ke kterým se přistupuje v proc, by tedy měly být definovány před vytvořením proc nebo předáním jako argument.
1 2 3 4 |
foo = 1 proc = Proc.new { foo } foo = 2 proc.call # => 2 |
Proměnnou však můžeme změnit poté, co byla definována v hlavním kontextu, protože vazba proc na ni drží odkaz, místo aby ji kopírovala.
1 2 3 |
foo = 1 Proc.new { foo = 2 }.call foo #=> 2 |
V tomto příkladu vidíme, že proměnná foo
ukazuje na stejný objekt v proc i mimo něj. Můžeme ji aktualizovat uvnitř proc, aby se aktualizovala i proměnná mimo něj.
Vazby
Pro sledování aktuálního oboru používá Ruby vazby, které zapouzdřují kontext provádění na každé pozici v kódu. Metoda binding
vrací objekt Binding
, který popisuje vazby na aktuální pozici.
1 2 |
foo = 1 binding.local_variables # => |
Objekt vazeb má metodu #local_variables
, která vrací jména všech lokálních proměnných dostupných v aktuálním oboru.
1 2 |
foo = 1 binding.eval("foo") # => 1 |
Kód lze na vazbě vyhodnotit pomocí metody #eval
. Výše uvedený příklad není příliš užitečný, protože prosté volání foo
by mělo stejný výsledek. Protože je však vazba objekt, který lze předávat, lze ji použít k některým zajímavějším věcem. Podívejme se na příklad.
Příklad ze života
Teď, když jsme se o vazbách dozvěděli v bezpečí naší garáže, jako bychom je vzali na svah a pohráli si s nimi na sněhu. Kromě interního používání vazeb v celém jazyce Ruby existují situace, kdy se objekty vazeb používají explicitně. Dobrým příkladem je šablonovací systém ERB-Ruby.
1 2 3 4 5 6 7 8 9 10 |
require 'erb' x = 1 def y 2 end template = ERB.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`" |
V tomto příkladu vytvoříme proměnnou s názvem x
, metodu s názvem y
a šablonu ERB, která na obojí odkazuje. Poté předáme aktuální vazbu ERB#result
, která vyhodnotí značky ERB v šabloně a vrátí řetězec s vyplněnými proměnnými.
Pod kapotou ERB používá Binding#eval
k vyhodnocení obsahu každé značky ERB v rozsahu předané vazby. Zjednodušená implementace, která funguje pro výše uvedený příklad, by mohla vypadat takto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class DiyErb def initialize(template) @template = template end def result(binding) @template.gsub(/<%=(.+?)%>/) do binding.eval() end end end x = 1 def y 2 end template = DiyErb.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`" |
Třída DiyErb
při inicializaci přebírá řetězec šablony. Její metoda #result
vyhledá všechny značky ERB a nahradí je výsledkem vyhodnocení jejich obsahu. Za tímto účelem zavolá Binding#eval
na předanou vazbu s obsahem značek ERB.
Předáním aktuální vazby při volání metody #result
může volání eval
přistupovat k proměnným definovaným mimo metodu, a dokonce i mimo třídu, aniž by je muselo explicitně předávat.
Ztratili jsme vás v lese?“
Doufáme, že se vám náš lyžařský výlet do lesa líbil. Hlouběji jsme se zabývali rozsahy a uzávěrami, poté co jsme je glosovali. Doufáme, že jsme vás v lese neztratili. Dejte nám prosím vědět, pokud byste se chtěli dozvědět více o vázání nebo máte jiné téma o Ruby, do kterého byste se rádi ponořili.
Děkujeme, že nás sledujete, a prosím, odkopněte sníh ze svých vázání, než je necháte dalšímu vývojáři. Pokud se vám tyto kouzelné výlety líbí, mohli byste se přihlásit k odběru Ruby Magic a dostávat e-mail, když přibližně jednou měsíčně vydáme nový článek.