在 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)
方式将方法作为参数传递。下面是一个非常简单的例子:
【讨论】:
我遇到了一个范围更复杂的方法的问题,终于想出了怎么做,希望这可以帮助某人:如果你的方法在另一个类中,你应该调用最后一个像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 中将方法作为参数传递的主要内容,如果未能解决你的问题,请参考以下文章