将方法作为参数传递给Ruby

Passing a method as a parameter in Ruby

我试图用Ruby搞砸一下。 因此,我尝试从"Programming Collective Intelligence"一书中实现算法(在Python中给出)。

在第8章中,作者将方法a作为参数传递。 这似乎适用于Python,但不适用于Ruby。

我有这个方法

1
2
3
def gaussian(dist, sigma=10.0)
  foo
end

并希望用另一种方法调用它

1
2
3
4
5
def weightedknn(data, vec1, k = 5, weightf = gaussian)
  foo
  weight = weightf(dist)
  foo
end

我得到的只是一个错误

1
ArgumentError: wrong number of arguments (0 for 1)

关于块和过程的注释是正确的,因为它们在Ruby中更常见。但是如果你愿意,你可以传递一个方法。你调用method来获取方法,然后用.call来调用它:

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


你想要一个proc对象:

1
2
3
4
5
6
7
8
9
10
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代码中设置默认值。

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

1
2
3
4
5
def weightedknn(data, vec1, k = 5, weightf = :gaussian)
  ...
  weight = self.send(weightf)
  ...
end

在这种情况下,您只是调用在对象上定义的方法,而不是传递完整的代码块。根据您的结构,您可能需要将self.send替换为object_that_has_the_these_math_methods.send

最后但并非最不重要的,你可以挂掉方法的块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)方式将方法作为参数传递。下面是一个非常简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
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


正常的Ruby方法是使用块。

所以它会是这样的:

1
2
3
4
5
def weightedknn( data, vec1, k = 5 )
  foo
  weight = yield( dist )
  foo
end

使用如下:

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

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


您可以在方法的method实例上使用&运算符将方法转换为块。

例:

1
2
3
4
5
6
7
8
9
10
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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  # 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,你可以像这样调用这个函数:

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

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

1
@categories = make_select_list( Category ) # selects all categories

对于Ruby块的强大功能如此之多。


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

1
weight = weightf.call( dist )

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


你也可以使用"eval",并将该方法作为字符串参数传递,然后在其他方法中简单地对其进行评估。