2017-01-26 18 views
2

Сегодня я с удивлением обнаружил, что рубин автоматически находит значения массива, заданные как параметр блока.Блок захвата блока Ruby или несколько параметров

Например:

foo = "foo" 
bar = "bar" 
p foo.chars.zip(bar.chars).map { |pair| pair }.first #=> ["f", "b"] 
p foo.chars.zip(bar.chars).map { |a, b| "#{a},#{b}" }.first #=> "f,b" 
p foo.chars.zip(bar.chars).map { |a, b,c| "#{a},#{b},#{c}" }.first #=> "f,b," 

я ожидал бы два последних примера, чтобы дать какие-то ошибки.

  1. Это пример более общей концепции в рубине?
  2. Не думаю, что моя формулировка в начале моего вопроса верна, что я называю тем, что здесь происходит?
+1

Это похоже на (может быть, такое же?), что происходит с параллельным назначением переменных из массива. См. Https://stackoverflow.com/questions/14913765/why-does-ruby-parallel-assignment-with-array-of-strings-returns-string – Max

ответ

5

блока механики Руби есть причуда к ним, то есть, если вы итерации то, что содержит массивы вы можете расширить их в различные переменные:

[ %w[ a b ], %w[ c d ] ].each do |a, b| 
    puts 'a=%s b=%s' % [ a, b ] 
end 

Эта модель является очень полезной при использовании Hash#each и вы хотите разбить key и value частей пары: each { |k,v| ... } очень распространен в коде Ruby.

Если ваш блок принимает более одного аргумента и, то элемент, который выполняется итерацией, представляет собой массив, тогда он переключает способ интерпретации аргументов. Вы всегда можете принудительно расширить:

[ %w[ a b ], %w[ c d ] ].each do |(a, b)| 
    puts 'a=%s b=%s' % [ a, b ] 
end 

Это полезно в тех случаях, когда все гораздо сложнее:

[ %w[ a b ], %w[ c d ] ].each_with_index do |(a, b), i| 
    puts 'a=%s b=%s @ %d' % [ a, b, i ] 
end 

Поскольку в данном случае это итерация массива и другой элемент, который пристегивается, так каждый элемент на самом деле является кортежем формы %w[ a b ], 0 внутри, который будет преобразован в массив, если ваш блок принимает только один аргумент.

Это так же принцип можно использовать при определении переменных:

a, b = %w[ a b ] 
a 
# => 'a' 
b 
# => 'b' 

Это фактически присваивает независимые значения a и b. Контраст с:

a, b = [ %w[ a b ] ] 
a 
# => [ 'a', 'b' ] 
b 
# => nil 
+0

Итак, правильный термин для того, что здесь происходит: «Ruby is расширение каждого массива в переменные a, b или c внутри блока, переданного методу экземпляра карты "? – mbigras

+2

@mbigras большинство языков ссылаются на это как «деструктурирование», ES6 теперь имеет это, LISP (http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node252.html), clojure (http : //blog.jayfields.com/2010/07/clojure-destructuring.html) и многие другие. – Anthony

+0

@akuhn Третий пример касается нескольких параметров, которые, если не разбиты, рассматриваются как массив. – tadman

4

Рубиновый блок изворотливый.

Правило такое, если блок принимает более одного аргумента, и он дает один объект, который отвечает на to_ary, тогда этот объект расширяется. Это приводит к тому, что массив в сравнении с получением кортежа, похоже, ведет себя одинаково для блоков, которые принимают два или более аргумента.

yield [a,b] по сравнению с yield a,b действительно отличаются друг от друга, если блок принимает только один аргумент или когда блок принимает переменное количество аргументов.

Позвольте мне продемонстрировать, как из того, что

def yield_tuple 
    yield 1, 2, 3 
end 

yield_tuple { |*a| p a } 
yield_tuple { |a| p [a] } 
yield_tuple { |a, b| p [a, b] } 
yield_tuple { |a, b, c| p [a, b, c] } 
yield_tuple { |a, b, c, d| p [a, b, c, d] } 

гравюр

[1, 2, 3] 
[1] 
[1, 2] 
[1, 2, 3] 
[1, 2, 3, nil] 

В то время как

def yield_array 
    yield [1,2,3] 
end 

yield_array { |*a| p a } 
yield_array { |a| p [a] } 
yield_array { |a, b| p [a, b] } 
yield_array { |a, b, c| p [a, b, c] } 
yield_array { |a, b, c, d| p [a, b, c, d] } 

печатает

[[1, 2, 3]] 
[[1, 2, 3]] 
[1, 2] # array expansion makes it look like a tuple 
[1, 2, 3] # array expansion makes it look like a tuple 
[1, 2, 3, nil] # array expansion makes it look like a tuple 

И, наконец, чтобы показать, что все в Ruby, использует утку-печатая

class A 
    def to_ary 
    [1,2,3] 
    end 
end 

def yield_arrayish 
    yield A.new 
end 

yield_arrayish { |*a| p a } 
yield_arrayish { |a| p [a] } 
yield_arrayish { |a, b| p [a, b] } 
yield_arrayish { |a, b, c| p [a, b, c] } 
yield_arrayish { |a, b, c, d| p [a, b, c, d] } 

отпечатки

[#<A:0x007fc3c2969190>] 
[#<A:0x007fc3c2969050>] 
[1, 2] # array expansion makes it look like a tuple 
[1, 2, 3] # array expansion makes it look like a tuple 
[1, 2, 3, nil] # array expansion makes it look like a tuple 

PS такое же поведение расширения массива применяется для proc замыканий, которые ведут себя как блоки, в то время как lambda замыкания ведут себя как методы.

3

Я бы ожидал, что последние два примера приведут к некоторой ошибке.

Это действительно так, если вы пройдете proc по методу. Уступая такой процедурный гораздо строже - он проверяет свою Арность и не пытается преобразовать аргумент массива в список аргументов:

def m(a, b) 
    "#{a}-#{b}" 
end 

['a', 'b', 'c'].zip([0, 1, 2]).map(&method(:m)) 
#=> wrong number of arguments (given 1, expected 2) (ArgumentError) 

Это происходит потому, что zip создает массив (массивы) и map только выходы каждый элемент, т.е.

yield ['a', 0] 
yield ['b', 1] 
yield ['c', 2] 

each_with_index с другой стороны работы:

['a', 'b', 'c'].each_with_index.map(&method(:m)) 
#=> ["a-0", "b-1", "c-2"] 

, потому что yiel ds два отдельных значения, элемент и его индекс, то есть

yield 'a', 0 
yield 'b', 1 
yield 'c', 2 
+1

Хорошая точка. Вы также можете использовать «лямбда», чтобы это продемонстрировать. '[1,2,3] .each & lambda {| a, b | } 'fail, но' [1,2,3] .each & proc {| a, b | } 'работает. – akuhn