【问题标题】:Optional argument after splat argumentsplat 参数后的可选参数
【发布时间】:2013-06-14 21:54:41
【问题描述】:

这是我的程序:

def calculate(*numbers, options = {})
  add(numbers)      if options[:add]
  subtract(numbers) if options[:add] == false
end

def add(*numbers)
  numbers.reduce(:+)
end

def subtract(*numbers)
  numbers.reduce(:-)
end

p calculate(1,2)

在第 1 行,它在抱怨

tests.rb:1:语法错误,意外 '=',期待 ')'
def 计算(*数字,选项 = {})
________________________________________________^
[0.1s 完成,退出码 1]

我认为这可能是 Ruby 中默认值的问题,因为在 v1.9 之前,您需要按顺序拥有所有默认值 - 但这不应该是问题,因为我的版本是

ruby 2.0.0p195 (2013-05-14) [i386-mingw32]

我尝试过将空格全部转置,因为 ruby​​ 在方法方面似乎对这些东西特别感兴趣,但没有骰子。

这可能是我的 splat 变量 *numbers 吗?

【问题讨论】:

  • 你可能想为 Ruby 2.0 做options: {}

标签: ruby


【解决方案1】:

splat 后不能有可选参数。

splat 的意思是“用完所有剩余的参数”,但是您提供了一个可选参数,那么解释器如何知道最后一个参数是“数字”splat 还是可选“选项”的一部分?

【讨论】:

    【解决方案2】:

    你只能在 splat 参数之后有强制参数。可选参数必须在 splat 之前。

    Ruby 中参数列表的伪正则表达式是这样的:

    mand* opt* splat? mand* (mand_kw | opt_kw)* kwsplat? block?
    

    这是一个例子:

    def foo(m1, m2, o1=:o1, o2=:o2, *splat, m3, m4, 
              ok1: :ok1, mk1:, mk2:, ok2: :ok2, **ksplat, &blk)
      Hash[local_variables.map {|var| [var, eval(var.to_s)] }]
    end
    
    method(:foo).arity
    # => -5
    
    method(:foo).parameters
    # => [[:req, :m1], [:req, :m2], [:opt, :o1], [:opt, :o2], [:rest, :splat], 
    #     [:req, :m3], [:req, :m4], [:keyreq, :mk1], [:keyreq, :mk2], 
    #     [:key, :ok1], [:key, :ok2], [:keyrest, :ksplat], [:block, :blk]]
    
    foo(1, 2, 3, 4)
    # ArgumentError: missing keywords: mk1, mk2
    
    foo(1, 2, 3, mk1: 4, mk2: 5)
    # ArgumentError: wrong number of arguments (3 for 4+)
    
    foo(1, 2, 3, 4, mk1: 5, mk2: 6)
    # => { m1: 1, m2: 2, o1: :o1, o2: :o2, splat: [], m3: 3, m4: 4, 
    #      ok1: :ok1, mk1: 5, mk2: 6, ok2: :ok2, ksplat: {}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)
    # => { m1: 1, m2: 2, o1: 3, o2: :o2, splat: [], m3: 4, m4: 5, 
    #      ok1: :ok1, mk1: 6, mk2: 7, ok2: :ok2, ksplat: {}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [], m3: 5, m4: 6, 
    #      ok1: :ok1, mk1: 7, mk2: 8, ok2: :ok2, ksplat: {}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5], m3: 6, m4: 7, 
    #      ok1: :ok1, mk1: 8, mk2: 9, ok2: :ok2, ksplat: {}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
    #      ok1: :ok1, mk1: 9, mk2: 10, ok2: :ok2, ksplat: {}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
    #      ok1: 9, mk1: 10, mk2: 11, ok2: :ok2, ksplat: {}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
    #      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
    #      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
    #      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14}, 
    #      blk: nil }
    
    foo(1, 2, 3, 4, 5, 6, 7, 8, 
          ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end
    # => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
    #      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14}, 
    #      blk: #<Proc:0xdeadbeefc00l42@(irb):15> }
    

    【讨论】:

    • 非常感谢。您编写的伪正则表达式是否有任何文档?看起来是正确的,但doc 中没有足够的信息来确定。
    • ISO Ruby 语言规范、RubySpec、Ruby 编程语言、Ruby 编程、文档和实验。大量的实验。主要是后者。
    • @JörgWMittag 嗨。只是出于好奇,设计师保留这样一条规则的原因是什么 - “你只能在 splat 参数之后有强制参数。可选参数必须在 splat 之前”?在 splat 之后保留可选参数是否存在任何实现问题?
    • @BKS:本身没有实现问题。 如果你能想出一个规则是什么意思,那么实现绝对是微不足道的。然而,在可选参数之后可以有强制参数的事实已经让人们感到困惑。 (例如,尽管 Ruby 清楚地证明了这一点,但有些 C♯ 程序员声称这是不可能的。)拥有两组可选可能会太令人困惑。但是你可以制定一个明确的规则,并且规则已经告诉你如何实现它:
    • 1) 左侧的必填项,2) 右侧的必填项,3) 左侧的可选项,4) 右侧的可选项,5) Splats,6) 关键字可以任意穿插在任何地方。这完全可以通过将规则翻译成代码来实现。
    【解决方案3】:

    感谢@maerics 和@JorgWMittag -

    当你有一个 splat 时,它会保留所有参数,这就是为什么它不喜欢我的第二个“选项”参数。我通过将参数更改为 -

    解决了这个问题
    def calculate(*arguments)
      options = arguments[-1].is_a?(Hash) ? arguments.pop : {}
      options[:add] = true if options.empty?
      return add(*arguments) if options[:add]
      return subtract(*arguments) if options[:subtract]
    end
    

    【讨论】:

    • 感谢分享这个方法!它帮助我为正在编写的 DSL 找到了一个简单而简洁的解决方案。
    • 您还可以通过在三元运算符返回的哈希中添加选项的默认值来缩短此方法:options = arguments[-1].is_a?(Hash) ? arguments.pop : {add: true}。这允许您删除方法的第二行。
    【解决方案4】:
    def calculate( *numbers, add: true )
      add ? add( *numbers ) : subtract( *numbers )
    end
    
    def add *numbers; numbers.reduce( 0, :+) end
    
    def subtract n1, n2; n1 - n2 end
    
    calculate 1, 2 #=> 3
    calculate 3, 1, add: false #=> 2
    

    【讨论】:

      【解决方案5】:

      查看extract_options! 在 Rails 背后的工作原理。

      http://simonecarletti.com/blog/2009/09/inside-ruby-on-rails-extract_options-from-arrays/

      【讨论】:

        【解决方案6】:

        实际上,从 Ruby 2.0 开始,您可以使用 关键字参数 来完成此操作。如果您像这样定义“计算”方法:

        def calculate(a, *b, **options)
          return a + b.inject(0, :+) if options[:add]
          return a + b.inject(0, :-) if options[:subtract]
          return 0
        end
        

        然后你可以用这个来调用那个方法:

        calculate(3, 4, -5, 3, -8, add: true)
        

        结果你会得到 -3。

        【讨论】:

          猜你喜欢
          • 2012-04-18
          • 2019-08-04
          • 2015-01-19
          • 2013-10-07
          • 2014-05-05
          • 2021-01-12
          • 1970-01-01
          • 2019-11-09
          • 2017-12-14
          相关资源
          最近更新 更多