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


117

我想和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)

Answers:


100

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

但这听起来像您在这里想要更多可重用的代码块。


1
我认为第二个选项是最好的选择(即使用Object.send()),缺点是您需要为所有类使用一个类(无论如何,这是您应该在OO中执行的操作:)。它比一直传递一个块(Proc)更干,甚至可以通过包装方法传递参数。
Jimmy Stenke,2009年

4
另外,如果要foo.bar(a,b)发送,则为foo.send(:bar, a, b)。该图示(*)运营商允许你这样做foo.send(:bar, *[a,b]),你应该找到你想拥有的论据任意lengthed阵列-假设杆法可以泡起来
xxjjnn

99

引用块和Procs的注释是正确的,因为它们在Ruby中更常见。但是您可以根据需要传递一个方法。您调用method以获取方法并.call进行调用:

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

3
这是有趣的。值得注意的是,method( :<name> )将方法名称转换为可调用符号时,只需调用一次。您可以将结果存储在变量或参数中,然后将其像其他变量一样一直传递给

1
或者,也许不用参数列表中的方法语法,而可以在调用方法时使用它,如下所示:weightedknn(data,vec1,k,method(:gaussian))
Yahya

1
这种方法比处理proc或block更好,因为您不必处理参数-它可以随方法的需要一起使用。
danuker

3
为了完成此操作,如果要传递在其他地方定义的方法,请执行SomewhereElse.method(:method_name)。太酷了!
medik '16

这可能是它自己的问题,但是,如何确定符号是否引用了函数或其他内容?我尝试过,:func.class但这只是一个symbol
安静的比赛,2015年

46

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

def double(a)
  返回* 2 
结束
=>无

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

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

7
我遇到了范围更复杂的方法的问题,并最终弄清楚了该怎么做,希望对您有所帮助:如果您的方法例如在另一个类中,则应调用最后一行代码,method_with_function_as_param(Class.method(:method_name),...)而不是method(:Class.method_name)
V Déhaye17年

多亏您的回答,我发现了名为的方法method。我过得很愉快,但我想这就是为什么我更喜欢功能语言,而无需进行此类杂技来获得想要的东西。无论如何,我挖红宝石
Ludovic Kuty

25

Ruby的常规方法是使用块。

因此,它将类似于:

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

和使用像:

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

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



1

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

weight = weightf.call( dist )

编辑:如评论中所述,这种方法是错误的。如果您使用的是Procs而不是常规功能,那么它将起作用。


1
当他weightf = gaussian在arg列表中进行操作时,它实际上是在尝试执行gaussian并将结果分配为weightf的默认值。该调用没有必需的参数和崩溃。因此weightf甚至还不是带有call方法的proc对象。
亚历克斯·韦恩,

1
这(即做错了,并解释了原因的评论)实际上使我完全理解了已接受的答案,所以谢谢!+1
rmcsharry19年

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

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块的强大功能来说是如此。


-5

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


1
这真的是一个坏习惯,永远不要做!
开发人员

@Developer为什么将其视为不良做法?
jlesse

在性能方面,它们eval可以执行任意代码,因此极易受到各种攻击。
开发人员
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.