Назначение & (амперсанд) в 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 представляют собой не что иное, как специальный синтаксис, который позволяет передавать метод в качестве аргумента другому методу.