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

我只是想和露比玩玩。因此,我尝试实现“编程集体智能”Ruby 一书中的算法(Python 中给出)。

在第八章中,作者传递了一个方法 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)
115215 次浏览

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

weight = weightf.call( dist )

编辑: 正如评论中解释的那样,这种方法是错误的。如果您使用的是 Procs 而不是普通函数,那么它就可以工作。

您需要一个 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

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

通常的 Ruby 方法是使用一个块。

所以应该是这样的:

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

用法如下:

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

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

指向块和 Procs 的注释是正确的,因为它们在 Ruby 中更常见。但是如果需要,可以传递一个方法。您调用 method来获取方法,而 .call调用它:

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

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

def double(a)
return a * 2
end
=> nil


def method_with_function_as_param( callback, number)
callback.call(number)
end
=> nil


method_with_function_as_param( method(:double) , 10 )
=> 20

我建议使用与号来访问函数中的命名块。按照 这篇文章中给出的建议,你可以写下这样的东西(这是我的工作程序中的一个真正的片段) :

  # 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 块的威力就到此为止了。

对于 将该方法转换为块,可以在方法的 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

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

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