Назначение & (амперсанд) в Ruby для процедур procs и call
Я заметил, что множество примеров, связанных с Ruby Procs, имеют в нем следующее и символ.
# Ruby Example
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n, &callback)
n.times do
callback.call
end
end
shout_n_times(3, &shout)
# prints 'Yolo!' 3 times
Мой вопрос в том, какова функциональная цель за символом? Похоже, что если я написал тот же самый точный код без &, он работает как ожидалось:
# Same code as previous without &
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n, callback)
n.times do
callback.call
end
end
shout_n_times(3, shout)
# prints 'Yolo!' 3 times
Ответы
Ответ 1
Эта статья дает хороший обзор различий.
Чтобы обобщить статью, Ruby позволяет использовать неявные и явные блоки. Более того, Ruby имеет блок, proc и лямбда.
Когда вы вызываете
def foo(block)
end
block
- просто аргумент метода. Аргумент ссылается на переменную block
, и как вы взаимодействуете с ней, зависит от типа передаваемого вами объекта.
def foo(one, block, two)
p one
p block.call
p two
end
foo(1, 2, 3)
1
NoMethodError: undefined method `call' for 2:Fixnum
from (irb):3:in `foo'
from (irb):6
from /Users/weppos/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
foo(1, Proc.new { 1 + 1 }, 3)
1
2
3
Но когда вы используете амперсанд &
в определении метода, блок принимает другое значение. Вы явно, определяющий метод принятия блока. И другие правила будут применяться (например, не более одного блока для каждого метода).
def foo(one, two, &block)
p one
p block.call
p two
end
Прежде всего, будучи блоком, подпись метода теперь принимает "два параметра и блок", а не "три параметра".
foo(1, 2, Proc.new { "from the proc" })
ArgumentError: wrong number of arguments (3 for 2)
from (irb):7:in `foo'
from (irb):12
from /Users/weppos/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
Это означает, что вы должны заставить третий аргумент быть блоком, передающим параметр с амперсандом.
foo(1, 2, &Proc.new { "from the proc" })
1
"from the proc"
2
Однако это очень необычный синтаксис. В Ruby методы с блоками обычно называются с помощью {}
foo(1, 2) { "from the block" }
1
"from the block"
2
или do end
.
foo(1, 2) do
"from the block"
end
1
"from the block"
2
Вернемся к определению метода. Ранее я упоминал, что следующий код является объявлением явного блока .
def foo(one, two, &block)
block.call
end
Методы могут неявно принимать блок. Неявные блоки вызываются с помощью yield
.
def foo(one, two)
p yield
end
foo(1, 2) { "from the block" }
Вы можете проверить, что блок передан с помощью block_given?
def foo(one, two)
if block_given?
p yield
else
p "No block given"
end
end
foo(1, 2) { "from the block" }
=> "from the block"
foo(1, 2)
=> "No block given"
Эти связанные с блоком функции не будут доступны, если вы объявите "блок" как простой аргумент (следовательно, без амперсанда), потому что это будет просто анонимный аргумент метода.
Ответ 2
В качестве дополнения я запомнил &
как знак перехода между block
и Proc
.
Чтобы преобразовать a block
в Proc
def foo(&p)
puts p.class
end
foo {} # => Proc
Чтобы преобразовать a Proc
в block
def bar
yield "hello"
end
p = Proc.new {|a| puts a }
bar &p # => hello
Ответ 3
Ну, когда у вас есть блок, если вы применяете &
перед блоком, он становится объектом Proc
и наоборот.
_unary &_
: это как-то связано с преобразованием вещей в блоки и из них. Если вы не берете ничего другого, помните, что когда вы видите унарный "&" в Ruby вы пытаетесь сделать что-то в блоке или сделать блок во что-то.
В первом примере в этой строке shout_n_times(3, &shout)
вы преобразовываете объект Proc
, на который ссылается переменная shoot
, на block
. а затем в списке параметров метода вы преобразуете его обратно в объект Proc
.
В вашем втором примере это работает, потому что вы передаете непосредственно объект Proc
в качестве аргумента метода, а затем вызываете #call
на нем.
Ответ 4
Разница в том, что в первом примере:
# Ruby Example
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n, &callback)
n.times do
callback.call
end
end
shout_n_times(3, &shout)
... ваш синтаксис вызова метода позволяет вам переписать определение метода следующим образом:
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n)
n.times do
yield
end
end
shout_n_times(3, &shout)
--output:--
Yolo!
Yolo!
Yolo!
Эти два утверждения:
shout = Proc.new { puts 'Yolo!' }
...
shout_n_times(3, &shout)
... эквивалентны:
shout_n_times(3) do
puts 'Yolo!'
end
И запись yield() в определении метода shout_n_times() вызывает блок, указанный после вызова метода:
method call +--start of block specified after the method call
| |
V V
shout_n_times(3) do
puts 'Yolo!'
end
^
|
+--end of block
Вы видите, что блок подобен методу, и блок передается как невидимый аргумент в вызове метода, после которого записывается блок. И внутри определения метода тот, кто написал определение метода, может выполнить блок с yield(). Блоки Ruby представляют собой не что иное, как специальный синтаксис, который позволяет передавать метод в качестве аргумента другому методу.