Назначение & (амперсанда) в Ruby для процессов и методов вызова

Я заметил, что во многих примерах, связанных с 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

4 ответов


в этой статье обеспечивает хороший обзор различия.

чтобы суммировать статью, 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

но когда вы используете амперсанд!--12--> в определении метода блок принимает другое значение. Ты явно определение метода для принятия блока. И другие правила будут применяться (например, не более одного блока на метод).

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 методы с блоками обычно называются using {}

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"

эти связанные с блоком функции не будут доступны, если вы объявите "блок" как простой аргумент (следовательно, без амперсанда), потому что это будет просто аргумент анонимного метода.


в качестве дополнения я заставляю себя вспомнить & как знак преобразования между block и Proc.

преобразование block to Proc

def foo(&p)
  puts p.class
end

foo {} # => Proc

преобразование Proc до block

def bar
  yield "hello"
end
p = Proc.new {|a| puts a }

bar &p # => hello

хорошо, когда у вас есть блок, если вы примените & перед блоком он становится Proc объект и наоборот.

_unary &_: это имеет какое-то отношение к преобразованию вещей в блоки и из блоков. Если вы больше ничего не забираете из этого, помните, что когда вы видите унарное "&" в Ruby, вы смотрите на то, чтобы сделать что-то в блок или сделать блок во что-то.

в первом примере в этой строке shout_n_times(3, &shout), вы преобразование


разница в том, что в вашем первом примере:

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