【问题标题】:How to determine if optional arguments are passed in Ruby如何确定是否在 Ruby 中传递了可选参数
【发布时间】:2013-10-19 23:55:20
【问题描述】:

如果我在 Ruby 中有以下方法:

def foo(arg1, arg2 = "bar")
  puts arg1
  puts arg2
end

有没有办法确定用户是否在方法中为arg2 传递了一个值?显然我可以将if arg2 == "bar" 添加到该方法中,但这并没有捕捉到用户自己手动传入"bar" 的情况。当然,我可以将默认值设置为任何用户都不会传入的默认值,但这样很快就会变得非常丑陋。

那里有什么优雅的东西吗?

【问题讨论】:

  • 我的理解是否正确,对于您的示例,您想知道foo 是否可以确定对它的调用是foo(arg1) 还是foo(arg1, "bar")
  • 是的,就是这样。

标签: ruby parameters optional-parameters


【解决方案1】:
def foo(arg1, arg2 = (arg2_not_passed = true; "bar"))
  puts arg1
  puts arg2
  puts 'arg2 was passed' unless arg2_not_passed
end

【讨论】:

  • 有趣;如果您能帮助理解a = (b = true; "something"),将不胜感激
  • 这只是一个 Ruby 表达式。它首先将b 设置为true,然后作为新语句执行字符串"something"。整个括号的值将是最后一条语句的值,因此它仍然按预期将a 设置为"something"。我相信您希望这是 arg2 = (arg2_was_passed = false; "bar"),但不是 true
  • 这真是天才!
【解决方案2】:
def foo(*args)
  case args.length
  when 1
    # arg2 was not given
    arg1, arg2 = args.first, "bar"
    ...
  when 2
    # arg2 was given
    arg1, arg2 = args
    ...
  else
    raise ArgumentError
  end
end

【讨论】:

    【解决方案3】:

    一种方法(不优雅但有趣)是使默认值具有唯一的object_id。如果默认值是像整数这样的不可变对象,它将不起作用。

    def c
      @c ||= "bar"
    end
    
    def fun(a,b = c)   
         puts  b.object_id == c.object_id ? "default" : "user" 
    end
    
    fun "foo"
    #=> "default"
    
    fun "foo", "bar"
    #=> "user"
    

    【讨论】:

    • 我喜欢!又被那些讨厌的不可变因素给挫败了。
    • 这就是 Rubinius 的做法,在它的许多方法实现中。在 YARV、JRuby 等中,它们是用 C、Java 等实现的,可以访问参数列表的内部实现,从而可以检查传递了多少参数。但是在 Rubinius 中,许多这些方法都是在纯 Ruby 中实现的,没有任何特殊功能,因此他们不得不求助于这样的技巧。在 Rubinius 中,有一个特殊的 undefined 方法,它返回一个带有不可伪造的 object_id 的对象,然后你可以在该方法中执行 undefined.equals?(b)
    【解决方案4】:

    我有办法!假设我们想要:

    def foo(arg1, arg2 = 42)
    

    那么我们将改为:

    def foo(arg1, arg2 = [42,KEY])
    

    如果agr2 == [42,KEY],我们断定调用方法没有传递第二个参数,在这种情况下默认42适用。如果 arg2 是其他任何东西,则 arg2 已通过。简单!

    我知道你在想什么:如果调用程序实际上通过[42,KEY] 会怎样?我也想通了:KEY = rand(100000000000000).

    我想再补充一件事。我原则上反对这样做,但我知道如果我不这样做,我会收到如此多的反对票,以至于我的名声会变成负面的。所以这里是::-)

    【讨论】:

    • 我只是想了类似的方法,发现你已经回答了。我想知道您为什么反对使用这种方法。例如写一个像def mymethod(param1, param2 = "not specified by user")这样的方法有什么不好?然后我可以在体内有param2 = "default value" if param2 == "not specified by user"
    • 我原则上不反对(尽管其他人可能)。我只是觉得这有点笨拙,最好重组代码以避免需要回答问题。
    【解决方案5】:

    我不确定这是否可行。考虑以下几点:

    class Test
      def method(a,b=1)
        local_variables.each do |var|
              puts "Setting #{var} to #{eval var.to_s}"
        end
      end
    end
    

    然后尝试在Test 的实例上调用method

    ?> t.method(1)
    Setting a to 1
    Setting b to 1
    => [:a, :b]
    
    ?> t.method(1,2)
    Setting a to 1
    Setting b to 2
    => [:a, :b]
    
    ?> t.method(1,1)
    Setting a to 1
    Setting b to 1
    => [:a, :b]
    

    那些不区分方法是如何被调用的。

    所以我建议@sawa 的方法与以下混合:

    raise ArgumentError, "Too many arguments" if args.length > 2
    

    因为您似乎希望将参数限制为 2,而 @sawa 的方法允许使用未定义的数字。

    这是针对非正统要求的非正统方法,因此请确保您方法的客户端了解 API 的工作原理。

    【讨论】:

    • 感谢您提供详细信息。在我的实际实现中,我只是要将第二个参数默认为nil(因为用户永远不会将nil 传递给它)然后将nil 与方法中的“默认”值交换,但我很好奇是否有更好的方法。
    • 我不明白为什么要迭代local_variables,而不仅仅是引用ab。不管怎样,这不只是展示了我们已经知道的吗?
    猜你喜欢
    • 2012-05-10
    • 2012-02-01
    • 2010-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-20
    • 2020-03-11
    • 1970-01-01
    相关资源
    最近更新 更多