Такая конструкция выполняет код “a”, т.е. просто значение локальной переменной a в области видимости vars. Поменять такую локальную переменную можно так же через eval:
eval “a = 101”, varsПоскольку область видимости — это объект, ее можно передавать в другие методы:
def value_of_a(vars) eval “a”, vars end def change_a(vars) eval “a = 101”, vars end def check a = 15 puts value_of_a(binding) change_a(binding) puts value_of_a(binding) end checkЭтот код выдаст: 15 101. Метод может вернуть свою область видимости, тем самым сохранив все свои локальные переменные.
Блок выполняется в той области видимости, где он определен:
a = 33 block = lambda { a } def redefine_a(b) a = 44 b.call end redefine_a(block) # => 33Что же здесь произошло? Блок block, просто возвращающий значение переменной a, определен в верхней области видимости. Перекрытие локальной переменной a в функции redefine_a ни коим образом не влияет на то, что видно этому блоку.
С помощью доступа до области видимости можно реализовать такую сложную для Ruby вещь, как обмен местами двух переменных.
def swap(var_a, var_b, vars) old_a = eval var_a, vars old_b = eval var_b, vars eval “#{var_a} = #{old_b}”, vars eval “#{var_b} = #{old_a}”, vars end a = 22 b = 33 swap (“a”, “b”, binding) p a # => 33 p b # => 22В верхней области видимости объекты будут переставлены. Есть одна только маленькая проблема: значения объектов a и b будут приведены к строке в данном примере, поэтому реально он неприменим для объектов, которые не могут восстановиться из своего строкового представления.
Предыдущий пример не будет работать с объектами, которые не могут быть сериализованы в строку. Перепишем его:
def swap(get_a, get_b, set_a, set_b) temp = get_a.call set_a.call(get_b.call) set_b.call(temp) end a = 22 b = 33 swap(lambda{a}, lambda{b}, lambda{|v| a=v}, lambda{|v| b=v}) p a # => 33 p b # => 22Что здесь получается? Все четыре блока, которые передаются в функцию swap, живут в верхней области видимости, поэтому оперируют именно верхними переменными. Но такой вариант слишком сложен в использовании. Им вообще пользоваться нельзя, потому что запомнить все эти блоки просто нельзя. Упростим.
Создадим объект Reference, который знает имя переменной и ее область видимости. Это поможет написать простую в использовании функцию swap:
Сам код класса Reference выглядит так:
class Reference def initialize(var_name, vars) @getter = eval “lambda { #{var_name} }”, vars @setter = eval “lambda { |v| #{var_name} = v }”, vars end def value @getter.call end def value=(new_value) @setter.call(new_value) end endВажно в этом коде то, что getter и setter — не просто блоки, а они создаются блоками в области видимости vars. Если бы не было этой конструкции с eval, это были бы два блока, созданных в локальной области видимости конструктора. Создание здесь блоков для выборки и установки значений нужно для того, что бы объект не приводился к строке.
Теперь все уже почти готово для создания функции swap, которая будет гораздо короче записываться. Но сначала опишем метод ref, который будет хитрым образом создавать объект Reference:
Очевидно, передаваемый блок должен возвращать название переменной и быть определенным в верхней области видимости. Тогда Reference получит не локальную для ref область видимости, а верхнюю, ту, в которой живут нужные переменные. Использоваться ref будет так:
Обратите внимание на скобки. Это именно фигурные скобки, создание блока. Тоже самое, что
aref = ref() do
return :a
end
Теперь, swap будет выглядеть и использоваться так:
Не могу сказать, что очень хорош тот код, который постоянно манипулирует областью видимости, однако надо отметить, что языковые извраты становятся нужны как воздух для создания простых и удобных DSL-ей или каких-то уж очень вычурных ситуаций. Так или иначе, про такую возможность использования Ruby неплохо знать и понимать, что происходит, когда встречается такое использование.
Статья переведена с оригинала на onestepback.org