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

问题描述 投票:0回答:9

我正在尝试学习 Ruby,想用 Ruby 实现《Programming Collective Intelligence》一书中的 Python 算法。

在第 8 章中,作者传递了一个方法作为参数,该方法似乎在 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)
ruby parameters methods
9个回答
117
投票

提到块和过程的注释是正确的,因为它们在 Ruby 中更常见。但如果你愿意的话,你可以传递一个方法。您调用

method
来获取该方法,然后
.call
来调用它:

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

109
投票

你想要一个 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 并在过程代码本身中设置默认值。


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

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

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


58
投票

您可以使用

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

def 双(a)
  返回*2
结尾
=> 无

def method_with_function_as_param(回调,数字)
  回调.call(号码)
结尾
=> 无

method_with_function_as_param( 方法(:double) , 10 )
=> 20

26
投票

执行此操作的普通 Ruby 方法是使用块。

所以它会是这样的:

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

使用方式如下:

weightedknn(data, vec1) { |dist| gaussian( dist ) }

此模式在 Ruby 中广泛使用。


17
投票

您可以在方法的

&
实例上使用
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


2
投票

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

weight = weightf.call( dist )

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


1
投票

我建议使用 & 符号来访问函数内的命名块。按照本文中给出的建议,您可以编写类似这样的内容(这是我工作程序中的真实片段):

  # 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

之后,你可以像这样调用这个函数:

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

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

@categories = make_select_list( Category ) # selects all categories

Ruby 块的强大功能就到此为止了。


0
投票

Proc方法调用类似,您也可以将 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

-8
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.