Passing a method as a parameter in Ruby

RubyParametersMethods

Ruby Problem Overview


I am trying to mess around a little bit with Ruby. Therefor I try to implement the algorithms (given in Python) from the book "Programming Collective Intelligence" Ruby.

In chapter 8 the author passes a method a as parameter. This seems to work in Python but not in Ruby.

I have here the method

def gaussian(dist, sigma=10.0)
  foo
end

and want to call this with another method

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

All I got is an error

ArgumentError: wrong number of arguments (0 for 1)

Ruby Solutions


Solution 1 - Ruby

The comments referring to blocks and Procs are correct in that they are more usual in Ruby. But you can pass a method if you want. You call method to get the method and .call to call it:

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

Solution 2 - Ruby

You want a proc object:

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

Just note that you can't set a default argument in a block declaration like that. So you need to use a splat and setup the default in the proc code itself.


Or, depending on your scope of all this, it may be easier to pass in a method name instead.

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

In this case you are just calling a method that is defined on an object rather than passing in a complete chunk of code. Depending on how you structure this you may need replace self.send with object_that_has_the_these_math_methods.send


Last but not least, you can hang a block off the method.

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

But it sounds like you would like more reusable chunks of code here.

Solution 3 - Ruby

You can pass a method as parameter with method(:function) way. Below is a very simple example:

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

Solution 4 - Ruby

The normal Ruby way to do this is to use a block.

So it would be something like:

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

And used like:

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

This pattern is used extensively in Ruby.

Solution 5 - Ruby

You can use the & operator on the Method instance of your method to convert the method to a block.

Example:

def foo(arg)
  p arg
end

def bar(&block)
  p 'bar'
  block.call('foo')
end

bar(&method(:foo))

More details at http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html

Solution 6 - Ruby

You have to call the method "call" of the function object:

weight = weightf.call( dist )

EDIT: as explained in the comments, this approach is wrong. It would work if you're using Procs instead of normal functions.

Solution 7 - Ruby

I would recommend to use ampersand to have an access to named blocks within a function. Following the recommendations given in this article you can write something like this (this is a real scrap from my working program):

  # 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, you can call this function like this:

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

If you don't need to filter your selection, you simply omit the block:

@categories = make_select_list( Category ) # selects all categories

So much for the power of Ruby blocks.

Solution 8 - Ruby

Similarly to a Proc or a method call, you can also pass a lambda as weightf parameter :

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

Solution 9 - Ruby

you also can use "eval", and pass the method as a string argument, and then simply eval it in the other method.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChristian Stade-SchuldtView Question on Stackoverflow
Solution 1 - RubyDaniel LucraftView Answer on Stackoverflow
Solution 2 - RubyAlex WayneView Answer on Stackoverflow
Solution 3 - RubyPatrick WongView Answer on Stackoverflow
Solution 4 - RubyChuckView Answer on Stackoverflow
Solution 5 - RubyGiovanni CappellottoView Answer on Stackoverflow
Solution 6 - RubyTiagoView Answer on Stackoverflow
Solution 7 - RubyvitrumsView Answer on Stackoverflow
Solution 8 - Rubyh0lyView Answer on Stackoverflow
Solution 9 - RubyKonstantinView Answer on Stackoverflow