我正在尝试学习 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 中更常见。但如果你愿意的话,你可以传递一个方法。您调用
method
来获取该方法,然后 .call
来调用它:
def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
...
weight = weightf.call( dist )
...
end
你想要一个 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
但听起来您希望这里有更多可重用的代码块。
您可以使用
method(:function)
方式将方法作为参数传递。下面是一个非常简单的例子:
def 双(a) 返回*2 结尾 => 无 def method_with_function_as_param(回调,数字) 回调.call(号码) 结尾 => 无 method_with_function_as_param( 方法(:double) , 10 ) => 20
执行此操作的普通 Ruby 方法是使用块。
所以它会是这样的:
def weightedknn(data, vec1, k = 5)
foo
weight = yield(dist)
foo
end
使用方式如下:
weightedknn(data, vec1) { |dist| gaussian( dist ) }
此模式在 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
你必须调用函数对象的“call”方法:
weight = weightf.call( dist )
编辑:正如评论中所解释的,这种方法是错误的。如果您使用 Procs 而不是普通函数,它会起作用。
我建议使用 & 符号来访问函数内的命名块。按照本文中给出的建议,您可以编写类似这样的内容(这是我工作程序中的真实片段):
# 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 块的强大功能就到此为止了。
您还可以使用“eval”,并将该方法作为字符串参数传递,然后在另一个方法中简单地对其进行评估。