views:

2706

answers:

5

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)
A: 

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.

Tiago
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.
Squeegy
Oh, I got it. Thanks for correcting me =)
Tiago
+4  A: 

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

wegihtedknn(foo, bar) do |dist|
  # square the dist
  dist * dist
end

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

Squeegy
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.
Jimmy Stenke
+2  A: 

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.

Chuck
+1  A: 

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
Daniel Lucraft