27.08.2009Приёмы работы с массивами и блоками в качестве аргументов в руби
Введение
Главная причина, по которой я испытываю нежные чувства к руби — это гибкость синтаксиса. Сегодня я хочу рассказать об особенностях передачи блоков и массивов в качестве параметров. Базовые вещи можно прочитать по ссылкам в последнем параграфе.
Массивы
О том, как принять неограниченное число аргументов, знают все:
$KCODE = "utf-8"
def my_args(*args)
puts args.inspect
end
my_args(1, 2, "собачка", "котик") # => [1, 2, "собачка", "котик"]
Но что, если сами объекты, которые необходимо передать в качестве аргументов, уже находятся в массиве по какой-то причине?
arr = ["собачка", "котик", "ёжик", "медвежонок"]
selection = arr.select{ |a| a.chars.to_a.length > 5 }
my_args(selection) # => [["собачка", "медвежонок"]]
Внезапно массив (args) стал двумерным, что неудивительно, т.к. метод интерпретировал переданный ему массив как первый аргумент типа Array. В данном случае, чтобы передать массив как список аргументов, нужно использовать ту же звезду:
my_args(*selection) # => ["собачка", "медвежонок"]
Ура!
Кстати, звезду можно использовать и при получении значения от функции. Например, в таком случае:
def get_values
["красный", "зелёный", "синий"]
end
r, g, b = get_values
puts r.inspect # => "красный"
puts g.inspect # => "зелёный"
puts b.inspect # => "синий"
r, gb = get_values
puts r.inspect # => "красный"
puts gb.inspect # => "зелёный"
r, *gb = get_values
puts r.inspect # => "красный"
puts gb.inspect # => ["зелёный", "синий"]
По-моему, здорово!
Блоки
Бывают случаи, когда блок нужно сохранить на будущее, чтобы потом его использовать. Тогда можно указать его в списке параметров метода, что, конечно же, не делает его обязательным само по себе.
class Button
def on_click(&block)
if block_given?
@stored_block = block
else
puts "Блока нет, но жизнь продолжается"
end
end
def click(*args)
@stored_block.call(*args)
end
end
my_btn = Button.new
my_btn.on_click # => Блока нет, но жизнь продолжается
my_btn.on_click do |*args|
puts "Произошёл клик!"
end
my_btn.click # => Произошёл клик!
Теперь предположим, опять же, что по каким-то причинам блок у нас уже есть. Он определён до вызова метода.
handler = lambda{ |*args| puts "Вот аргументы: #{args.inspect}" }
my_btn.on_click(handler) # => wrong number of arguments (1 for 0) (ArgumentError)
То есть наш блок превратился в аргумент типа Proc. А метод совсем этого не ожидал. Можно модифицировать сам метод, чтобы он выдерживал подобные условия, но не для этого я всё это пишу :) Если мы чему-нибудь научились в предыдущей главке, то решение по аналогии приходит само:
my_btn.on_click(&handler)
my_btn.click("усы", "хвост") # => Вот аргументы: ["усы", "хвост"]
Надеюсь, что было полезно.