145

I am trying to learn Ruby, and want to implement the Python algorithms from the book "Programming Collective Intelligence" in Ruby.

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

I am using the method:

def gaussian(dist, sigma=10.0) foo end 

and want to call this using another method:

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

But I got is an error:

ArgumentError: wrong number of arguments (0 for 1) 

9 Answers 9

119

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 
Sign up to request clarification or add additional context in comments.

6 Comments

This is interesting. It's worth noting that you call method( :<name> ) just once when converting your method name to a callable symbol. You can store that result in a variable or a parameter and keep passing it to child functions like any other variable from then on...
Or maybe, instead of using the method syntax in the argument list, you can use it while invoking the method as follows : weightedknn( data, vec1, k, method( :gaussian) )
This method is better than mucking around with a proc or block, since you don't have to handle parameters - it just works with whatever the method wants.
For completion, if you want to pass a method defined somewhere else, do SomewhereElse.method(:method_name). That's pretty cool!
This may be its own question but, how can I determine if a symbol references a function or something else? I tried :func.class but that's just a symbol
|
112

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.

2 Comments

I think that second option is the best option (that is, using Object.send()), the drawback is that you need to use a class for all of it (which is how you should do in OO anyway :)). It is more DRY than passing a block (Proc) all the time, and you could even pass arguments trough the wrapper method.
As an addition, if you want to do foo.bar(a,b) with send, it is foo.send(:bar, a, b). The splat (*) operator allows you to do foo.send(:bar, *[a,b]) should you find you want to have an arbitrary lengthed array of arguments - assuming the bar method can soak them up
59

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 

2 Comments

I faced an issue for a method with a more complicated scope, and finally figured out how to do, hope this can help someone : If your method is for example in another class, you should call the last line of code like method_with_function_as_param(Class.method(:method_name),...) and not method(:Class.method_name)
Thanks to your answer, I discovered the method called method. Made my day but I guess that's why I prefer functional languages, no need to make such acrobatics to get what you want. Anyway, I dig ruby
26

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:

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

This pattern is used extensively in Ruby.

Comments

17

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

Comments

2

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.

2 Comments

When he does weightf = gaussian in the arg list it's actually trying to execute gaussian and assign the result as the default value of weightf. The call doesn't have required args and crashes. So weightf is not even a proc object with a call method just yet.
This (ie. doing it wrong and the comment explaining why) actually allowed me to fully understand the accepted answer, so thanks! +1
1

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.

Comments

0

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 

Comments

-8

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

3 Comments

This is really bad practice, never do it!
@Developer why is this considered bad practice?
Among performance reasons, the eval can execute arbitrary code so it is extremely vulnerable to various attacks.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.