Ruby可选参数

124
如果我这样定义一个Ruby函数:

If I define a Ruby functions like this:

def ldap_get ( base_dn, filter, scope=LDAP::LDAP_SCOPE_SUBTREE, attrs=nil )

如何仅使用前两个参数和最后一个参数进行调用?为什么不能使用以下方式调用:

ldap_get( base_dn, filter, , X)

是否可能,如果可能的话,该如何实现?

8个回答

137

使用选项哈希通常更好。

def ldap_get(base_dn, filter, options = {})
  options[:scope] ||= LDAP::LDAP_SCOPE_SUBTREE
  ...
end

ldap_get(base_dn, filter, :attrs => X)

23
常见的策略是设定一个默认选项哈希,并将传入的任何内容合并进去:options = default_options.merge(options) - Nathan Long
7
我不赞成这样做,因为选项没有告诉你该方法需要什么参数或默认值是什么。 - Bron Davies

131

目前 Ruby 不支持此功能。您无法向方法传递“空”属性。最接近的方法是传递 nil:

ldap_get(base_dn, filter, nil, X)

然而,这将会将作用域设置为 nil 而不是 LDAP::LDAP_SCOPE_SUBTREE。

你可以在方法内部设置默认值:

def ldap_get(base_dn, filter, scope = nil, attrs = nil)
  scope ||= LDAP::LDAP_SCOPE_SUBTREE
  ... do something ...
end

现在,如果按照上述方法调用该方法,则行为将与您期望的一样。


22
这个方法有一个小陷阱:比如,如果你想让scope的默认值为true,并传入了false,那么scope ||= true就不能正常工作。它会被认为是nil,然后被赋值为true - Joshua Pinter
4
现在的 Ruby 版本,距离这个回答已经过去三年了,是否可以实现这个功能? - dalloliogm
1
@JoshPinter,解释得很好。基本上||=不是a=b或c,看到xyz ||= true我感到不舒服。它的意思是如果它是nil,它总是true。如果它是true,那么它就是true。 - Damon Aw
7
在众人都说scope ||= true很糟糕时,我很惊讶没有人提到更好的方法是使用scope = LDAP::LDAP_SCOPE_SUBTREE if scope.nil?。当然,前提是假定nil是无效值。 - Erik Sandberg
1
更新这个老掉牙的方法:另一种选择是使用下划线符号表示。不幸的是,它与将参数设置为“nil”的效果相同。有些人可能喜欢这种表示法:ldap_get(base_dn, filter, _, X)(注意:我还不知道这是何时引入Ruby的。有趣的SO线程)。 - Eric Platon
显示剩余4条评论

54

时间已经过去,自从版本2起Ruby支持命名参数:

def ldap_get ( base_dn, filter, scope: "some_scope", attrs: nil )
  p attrs
end

ldap_get("first_arg", "second_arg", attrs: "attr1, attr2") # => "attr1, attr2"

1
您还可以使用双星号来收集额外的未定义关键字参数。这与此问题有关:https://dev59.com/C2kv5IYBdhLWcg3w9lai#35259850 - Henry Tseng

3

按照你定义的方式,无法实现ldap_get。但是,如果你像这样定义ldap_get

def ldap_get ( base_dn, filter, attrs=nil, scope=LDAP::LDAP_SCOPE_SUBTREE )

现在您可以:
ldap_get( base_dn, filter, X )

但是现在你遇到了一个问题,你不能使用前两个参数和最后一个参数来调用它(与之前的问题相同,但现在最后一个参数不同)。

这背后的原理很简单:Ruby中的每个参数都不需要有默认值,因此您无法按照指定的方式调用它。例如,在您的情况下,前两个参数没有默认值。


1

1) 你不能重载方法(为什么Ruby不支持方法重载?),那么为什么不完全编写一个新的方法呢?

2) 我使用了零个或多个长度的数组的扩展运算符*解决了类似的问题。然后,如果我想传递一个或多个参数,它会被解释为一个数组,但如果我想调用没有任何参数的方法,则不需要传递任何内容。请参见Ruby编程语言第186/187页。


0

最近我找到了一个解决方法。我想在数组类中创建一个带有可选参数的方法,以保留或丢弃数组中的元素。

我模拟的方法是通过将数组作为参数传递,并检查该索引处的值是否为nil。

class Array
  def ascii_to_text(params)
    param_len = params.length
    if param_len > 3 or param_len < 2 then raise "Invalid number of arguments #{param_len} for 2 || 3." end
    bottom  = params[0]
    top     = params[1]
    keep    = params[2]
    if keep.nil? == false
      if keep == 1
        self.map{|x| if x >= bottom and x <= top then x = x.chr else x = x.to_s end}
      else
        raise "Invalid option #{keep} at argument position 3 in #{p params}, must be 1 or nil"
      end
    else
      self.map{|x| if x >= bottom and x <= top then x = x.chr end}.compact
    end
  end
end

使用不同的参数尝试我们的类方法:

array = [1, 2, 97, 98, 99]
p array.ascii_to_text([32, 126, 1]) # Convert all ASCII values of 32-126 to their chr value otherwise keep it the same (That's what the optional 1 is for)

输出:["1", "2", "a", "b", "c"]

好的,很棒,这正如计划中的那样。现在让我们来看看如果在数组中不传入第三个参数选项(1)会发生什么。

array = [1, 2, 97, 98, 99]
p array.ascii_to_text([32, 126]) # Convert all ASCII values of 32-126 to their chr value else remove it (1 isn't a parameter option)

输出:["a", "b", "c"]

正如您所看到的,数组中的第三个选项已被删除,从而启动方法中的不同部分并删除所有不在我们范围内的ASCII值(32-126)

或者,我们可以在参数中将该值发出为nil。代码块如下:

def ascii_to_text(top, bottom, keep = nil)
  if keep.nil?
    self.map{|x| if x >= bottom and x <= top then x = x.chr end}.compact
  else
    self.map{|x| if x >= bottom and x <= top then x = x.chr else x = x.to_s end}
end

-1

可以的 :) 只需要改变定义

def ldap_get ( base_dn, filter, scope=LDAP::LDAP_SCOPE_SUBTREE, attrs=nil )

def ldap_get ( base_dn, filter, *param_array, attrs=nil )
scope = param_array.first || LDAP::LDAP_SCOPE_SUBTREE

现在,作用域将位于数组的第一个位置。 当您提供3个参数时,将分配base_dn、filter和attrs,并且param_array将为[] 当有4个或更多参数时,param_array将为[argument1,or_more,and_more]

缺点是...这是一个不清晰的解决方案,非常丑陋。这是为了回答在Ruby函数调用中可能省略参数的问题 :)

另一件事情是要重写作用域的默认值。


4
这个解决方案完全错误。在使用展开运算符(*param_array)后,在默认值参数(attrs=nil)中设置默认值是语法错误。 - Erik Sandberg
3
-1:Erik 是正确的。在 irb 2.0.0p247 中会导致语法错误。根据《Ruby编程语言》所述,在 Ruby 1.8 中,星号参数必须放在最后,除非紧随其后的是一个&parameter,但在 Ruby 1.9 中,它也可以跟在“普通参数”之后。在任何情况下,带有默认值的参数都不能在星号参数之后声明。 - andyg0808
Ruby编程语言第186/187页中,splat可以与方法一起使用。它必须是方法中的最后一个参数,除非使用&。 - rupweb
所以AndyG是正确的,顺序应该是:def ldap_get (base_dn, filter, attrs=nil, *param_array)。 - rupweb

-1

你可以使用部分应用来实现这个,但是使用命名变量肯定会导致更易读的代码。John Resig在2008年写了一篇关于如何在JavaScript中实现它的博客文章:http://ejohn.org/blog/partial-functions-in-javascript/

Function.prototype.partial = function(){
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function(){
    var arg = 0;
    for ( var i = 0; i < args.length && arg < arguments.length; i++ )
      if ( args[i] === undefined )
        args[i] = arguments[arg++];
    return fn.apply(this, args);
  };
};

在 Ruby 中应用相同的原则可能是可行的(除了原型继承)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接