【问题标题】:What's a clean way to allow a block to handle a variable number of arguments?什么是允许块处理可变数量参数的干净方法?
【发布时间】:2012-04-16 20:29:00
【问题描述】:

我以前遇到过这种情况,但有件事告诉我,我通常处理它的方式不是最干净或最惯用的。

假设我有一个接受一个块的函数,该块又可以接受 1 或 2 个(比如说)参数。

def with_arguments(&block)
  case block.arity
  when 1
    block.call("foo")
  when 2
    block.call("foo", "bar")
  end
end

with_arguments do |x|
  puts "Here's the argument I was given: #{x}"
end

with_arguments do |x, y|
  puts "Here are the arguments I was given: #{x}, #{y}"
end

打开arity 似乎很老套。有没有更标准的 Ruby 方式来实现这种事情?

【问题讨论】:

  • 你不能把方法分成with_one_argumentwith_two_arguments 之类的东西吗(当然它们不会真的这么叫)?

标签: ruby block proc


【解决方案1】:

这是我将任意参数传递给lambda 的方法:

def with_arguments(&block)
  args = %w(foo bar)
  n = block.arity
  block.call *(n < 0 ? args : args.take(n))
end

with_arguments &lambda { |foo| }
with_arguments &lambda { |foo, bar| }
with_arguments &lambda { |*args| }
with_arguments &lambda { |foo, *args| }
with_arguments &lambda { |foo, bar, *args| }

如果n 为负数,则lambda 采用任意数量的参数。正是(n + 1).abs 这些参数是强制性的。人们可以使用该信息来决定要传递哪些参数。

如果lambda 接受有限数量的参数,则只需传递args 的第一个n 元素。如果它接受任意数量的参数,则只需传递整个参数数组。

lambda本身会处理args不足的情况:

with_arguments &lambda { |foo, bar, baz, *args| }
# ArgumentError: wrong number of arguments (2 for 3)

您可以简单地将两个参数传递给块:

def with_arguments(&block)
  block.call 'foo', 'bar'
end

with_arguments { |x| puts x }              # y is not used
with_arguments { |x, y| puts x, y }        # All arguments are used
with_arguments { |x, y, z| puts x, y, z }  # z will be nil

未使用的块参数被丢弃,任何额外的参数都将设置为nil

This is specific to regular blocks and Procs - 如果给定错误数量的参数,lambdas 将引发错误。您实际上可以通过调用Proc#lambda?来了解是否是这种情况

另外,如果你不打算存储块,直接使用yield 会更简洁:

def with_arguments
  yield 'foo', 'bar'
end

【讨论】:

  • 同样使用yield更快。
  • 啊,我又一次被不包含与我的用例完全匹配的示例代码所困扰。这当然是一个有用的答案;但事实证明,我主要关心的是lambdas。对这个限制有什么想法吗?
  • @MatheusMoreira:不错的更新。我在我自己的代码中已经对类似的东西进行了准(嗯,暂时)解决:def call_with_args(block, *args); block.call(*args.slice(0, block.arity)); end
【解决方案2】:

一些解决方案...

  • 总是传递 2 个参数,即使一个必须为 nil
  • 只传递一个数组,它可能有更多或更少的元素。这种方式为您提供了两全其美:由于多重分配的工作方式,您可以省略或提供额外的块参数而不会发出警告

【讨论】:

    【解决方案3】:
    def bar(&block)
        puts 'In bar'
        block.call(1) if block
        puts 'Back in bar'
        block.call(1,2) if block
    end
    
    1.9.3p392 :043 > bar do |*b| puts b.length end
    In bar
    1
    Back in bar
    2
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多