【问题标题】:Ruby block taking array or multiple parametersRuby 块采用数组或多个参数
【发布时间】:2017-06-11 10:23:37
【问题描述】:

今天我惊讶地发现 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. 这是 ruby​​ 中更一般概念的示例吗?
  2. 我认为我在问题开头的措辞不正确,我该怎么称呼这里发生的事情?

【问题讨论】:

标签: ruby block enumerable


【解决方案1】:

Ruby 块就是那样古怪。

规则是这样的,如果一个块接受多个参数并且产生一个响应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 闭包的行为类似于方法。

【讨论】:

    【解决方案2】:

    Ruby 的块机制有一个怪癖,那就是如果您要迭代包含数组的东西,您可以将它们扩展成不同的变量:

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

    这种模式在使用 Hash#each 时非常有用,并且您想拆分 keyvalue 对的部分: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 的元组,如果你的block 只接受一个参数。

    这与定义变量时可以使用的原理大致相同:

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

    这实际上为ab 分配了独立的值。对比:

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

    【讨论】:

    • 那么正确的说法是:“Ruby 正在将每个数组扩展为传递给 map 实例方法的块内的变量 a、b 或 c”?
    • @mbigras 大多数语言将其称为“解构”,ES6 现在有它,LISP (cs.cmu.edu/Groups/AI/html/cltl/clm/node252.html)、clojure (blog.jayfields.com/2010/07/clojure-destructuring.html) 等等。
    • yield [a, b]yield a, b 之间存在差异,尽管当块只需要一个参数时它们的行为方式不同。
    • 内部元素不是[%w[a b], 0],请参阅[ %w[ a b ], %w[ c d ] ].each { |a| p a },它只打印没有索引的元素。在内部 each_with_index 生成块的元组而不是数组。所以它使用 yield each, index 而不是 yield [each, index]
    • 是的,我明白你在说什么。我喜欢你的回答,但你真的应该将输出移到更接近示例的位置,这样更容易理解。我也试图以更抽象的方式描述这里的行为,元组和数组之间的微妙之处很难表达。我再次更新了措辞。
    【解决方案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"]
    

    因为它产生两个单独的值,元素及其索引,即

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

    【讨论】:

    • 好点。您也可以使用lambda 来证明这一点。 [1,2,3].each &amp;lambda { |a,b| } 失败,但 [1,2,3].each &amp;proc { |a,b| } 有效。
    猜你喜欢
    • 1970-01-01
    • 2012-04-14
    • 1970-01-01
    • 2016-02-27
    • 1970-01-01
    • 1970-01-01
    • 2018-12-25
    • 1970-01-01
    • 2019-04-22
    相关资源
    最近更新 更多