在 Ruby 中将方法作为参数传递

Posted

技术标签:

【中文标题】在 Ruby 中将方法作为参数传递【英文标题】:Passing a method as a parameter in Ruby 【发布时间】:2010-10-06 01:58:00 【问题描述】:

我正在尝试对 Ruby 进行一些处理。因此,我尝试实现“编程集体智能”Ruby 一书中的算法(用 Python 给出)。

在第 8 章中,作者传递了一个方法 a 作为参数。这似乎在 Python 中有效,但在 Ruby 中无效。

我这里有方法

def gaussian(dist, sigma=10.0)
  foo
end

并想用另一种方法调用它

def weightedknn(data, vec1, k = 5, weightf = gaussian)
  foo
  weight = weightf(dist)
  foo
end

我得到的只是一个错误

ArgumentError: wrong number of arguments (0 for 1)

【问题讨论】:

【参考方案1】:

引用块和 Procs 的 cmets 是正确的,因为它们在 Ruby 中更常见。但是如果你愿意,你可以传递一个方法。你调用method 获取方法,调用.call 调用它:

def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
  ...
  weight = weightf.call( dist )
  ...
end

【讨论】:

这很有趣。值得注意的是,在将方法名称转换为可调用符号时,您只调用了一次 method( :<name> )。您可以将该结果存储在变量或参数中,然后像任何其他变量一样继续将其传递给子函数... 或者,您可以在调用方法时使用它,而不是在参数列表中使用 method 语法: weightedknn( data, vec1, k, method( :gaussian) ) 此方法比使用 proc 或块更好,因为您不必处理参数 - 它只适用于方法想要的任何内容。 为了完成,如果你想传递在其他地方定义的方法,请执行SomewhereElse.method(:method_name)。太酷了! 这可能是它自己的问题,但是,我如何确定符号是否引用了函数或其他内容?我试过:func.class,但这只是symbol【参考方案2】:

你想要一个 proc 对象:

gaussian = Proc.new do |dist, *args|
  sigma = args.first || 10.0
  ...
end

def weightedknn(data, vec1, k = 5, weightf = gaussian)
  ...
  weight = weightf.call(dist)
  ...
end

请注意,您不能在这样的块声明中设置默认参数。因此,您需要使用 splat 并在 proc 代码本身中设置默认值。


或者,根据您的所有范围,传入方法名称可能更容易。

def weightedknn(data, vec1, k = 5, weightf = :gaussian)
  ...
  weight = self.send(weightf)
  ...
end

在这种情况下,您只是调用在对象上定义的方法,而不是传入完整的代码块。根据您的结构,您可能需要将 self.send 替换为 object_that_has_the_these_math_methods.send


最后但并非最不重要的一点是,您可以在方法上挂起一个块。

def weightedknn(data, vec1, k = 5)
  ...
  weight = 
    if block_given?
      yield(dist)
    else
      gaussian.call(dist)
    end
  end
  ...
end

weightedknn(foo, bar) do |dist|
  # square the dist
  dist * dist
end

但听起来您希望这里有更多可重用的代码块。

【讨论】:

我认为第二个选项是最好的选择(即使用 Object.send()),缺点是你需要为所有它使用一个类(这是你应该做的无论如何在 OO 中:))。它比一直传递一个块(Proc)更干,你甚至可以通过包装方法传递参数。 另外,如果你想用send做foo.bar(a,b),那就是foo.send(:bar, a, b)。 splat (*) 运算符允许您执行 foo.send(:bar, *[a,b]) 如果您发现您想要一个任意长度的参数数组 - 假设 bar 方法可以吸收它们【参考方案3】:

您可以使用method(:function) 方式将方法作为参数传递。下面是一个非常简单的例子:

定义双(一) 返回一个 * 2 结尾 => 无 def method_with_function_as_param(回调,数字) callback.call(号码) 结尾 => 无 method_with_function_as_param(方法(:双),10) => 20

【讨论】:

我遇到了一个范围更复杂的方法的问题,终于想出了怎么做,希望这可以帮助某人:如果你的方法在另一个类中,你应该调用最后一个像method_with_function_as_param(Class.method(:method_name),...) 这样的代码行而不是method(:Class.method_name) 感谢您的回答,我发现了名为method的方法。度过了我的一天,但我想这就是我更喜欢函数式语言的原因,不需要为了得到你想要的东西而做这样的杂技。不管怎样,我挖红宝石【参考方案4】:

正常的 Ruby 方法是使用块。

所以它会是这样的:

def weightedknn( data, vec1, k = 5 )
  foo
  weight = yield( dist )
  foo
end

并像这样使用:

weightenknn( data, vec1 )  |dist| gaussian( dist ) 

这种模式在 Ruby 中被广泛使用。

【讨论】:

【参考方案5】:

您可以在方法的Method 实例上使用& 运算符将方法转换为块

例子:

def foo(arg)
  p arg
end

def bar(&block)
  p 'bar'
  block.call('foo')
end

bar(&method(:foo))

更多详情http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html

【讨论】:

【参考方案6】:

你必须调用函数对象的方法“call”:

weight = weightf.call( dist )

编辑:如 cmets 中所述,这种方法是错误的。如果您使用 Procs 而不是普通函数,它会起作用。

【讨论】:

当他在 arg 列表中执行 weightf = gaussian 时,它实际上是在尝试执行 gaussian 并将结果分配为 ​​weightf 的默认值。该调用不需要 args 和崩溃。所以 weightf 甚至还不是一个带有 call 方法的 proc 对象。 这(即做错和解释原因的评论)实际上让我完全理解了接受的答案,所以谢谢! +1【参考方案7】:

我建议使用 & 来访问函数中的命名块。按照this article 中给出的建议,您可以编写类似这样的内容(这是我工作程序中的真正废品):

  # Returns a valid hash for html form select element, combined of all entities
  # for the given +model+, where only id and name attributes are taken as
  # values and keys correspondingly. Provide block returning boolean if you
  # need to select only specific entities.
  #
  # * *Args*    :
  #   - +model+ -> ORM interface for specific entities'
  #   - +&cond+ -> block |x| boolean, filtering entities upon iterations
  # * *Returns* :
  #   - hash of entity.id => entity.name
  #
  def make_select_list( model, &cond )
    cond ||= proc  true  # cond defaults to proc  true 
    # Entities filtered by cond, followed by filtration by (id, name)
    model.all.map do |x|
      cond.( x ) ?  x.id => x.name  : 
    end.reduce Hash.new do |memo, e| memo.merge( e ) end
  end

Afterwerds,你可以这样调用这个函数:

@contests = make_select_list Contest do |contest|
  logged_admin? or contest.organizer == @current_user
end

如果您不需要过滤您的选择,您只需省略该块:

@categories = make_select_list( Category ) # selects all categories

Ruby 块的强大功能如此之多。

【讨论】:

【参考方案8】:

与Proc 或method call 类似,您也可以将lambda 作为weightf 参数传递:

def main
  gaussian = -> (params) 
    ...
  
  weightedknn(data, vec1, k = 5, gaussian, params)
  # Use symbol :gaussian if method exists instead
end

def weightedknn(data, vec1, k = 5, weightf, params)
  ...
  weight = weightf.call(params)
  ...
end

【讨论】:

【参考方案9】:

您也可以使用“eval”,并将方法作为字符串参数传递,然后在其他方法中简单地对其进行评估。

【讨论】:

这是非常糟糕的做法,千万不要这样做! @Developer 为什么这被认为是不好的做法? 在性能原因中,eval 可以执行任意代码,因此极易受到各种攻击。

以上是关于在 Ruby 中将方法作为参数传递的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中将类型作为方法参数传递

在辅助方法中将 self 作为参数传递

如何在 Python 中将方法作为参数传递

如何在python类中将方法名称作为参数传递

在 Postman 中将类对象作为参数传递

在 PHP 中将实例方法作为参数传递