4

The idea is similar to what you would do with a decorator in python, I don't know what arguments the method takes so i'm passing *args, **kwargs as a general case

The problem arises when I want to pass an empty hash as kwargs to a method that takes no arguments

def my_method
  'I have run correctly'
end

args = []
kwargs = {}

my_method *args, **kwargs
=> ArgumentError: wrong number of arguments (given 1, expected 0)

Interestingly, only the kwargs variable seems to be the problem since:

my_method *args, **{}
=> 'I have run correctly'

If you can suggest a way to actually pass a method object as a parameter to a function, that would be perfect too.

Asone Tuhid
  • 539
  • 4
  • 13
  • What do you mean by method object? – grail Mar 26 '17 at 03:41
  • You could write `def receive_method(m); m.call('hi'); end; q = method(:puts) #=> #; receive_method(q) #=> 'hi'`. See [Object#method](https://ruby-doc.org/core-2.2.0/Object.html#method-i-method). – Cary Swoveland Mar 26 '17 at 03:59
  • This is a well-known bug that has been asked and answered on [so] already, and has also been filed in the Ruby Issue Tracker. If I have some time later, I can dig up the reference, but if my memory is correct, this question is likely a duplicate. – Jörg W Mittag Mar 26 '17 at 12:18
  • The link to the bug report is in the accepted answer to the duplicate question. – Jörg W Mittag Mar 26 '17 at 12:42

1 Answers1

4

You can't pass an arbitrary number of arguments to a ruby method without declaring it in the method definition. Passing just *args works because since *args is empty, it's basically as though you're passing no arguments to the methods, which is what my_method expects. With kwargs, you're passing the empty hash as an argument. Since the method is declared to take no arguments, this raises the error.

In general, the way to do this in ruby would be something like this:

def my_method(*args, **kwargs)
  'I have run correctly'
end

If a ruby method can accept an indefinite number of arguments, or keyword arguments, it needs to be explicit in the definition of the method.

More frequently however, you'll see idioms like this:

def my_method(arg, opts={})
  # do something
end

# Invoke the method like this:
# args == ['argument', 'another argument']
# opts == {opt1: 'val1', opt2: 'val2'}
my_method('some argument', opt1: 'val1', opt2: 'val2')

As to your observation about my_method *args, **kwargs acting differently than my_method *args, **{}... I'll first say that if your motivation is first and foremost about learning Ruby, then I would reemphasize my above point that kwargs are not a commonly used in Ruby, at least not in the same way they are used in Python.

And after that I'll say that this is very strange.

I notice that:

[**kwargs] # => [{}]
[**{}] # => []
# And even more interesting:
[**{}.itself] # => [{}]

So I was correct in saying that with **kwargs, the interpreter complains because you're passing the empty hash to a method that doesn't expect any arguments. But as to why that is different from **{}, I'm not sure. I'm digging into now, but my current suspicion is honestly that this is a fluke of the interpreter. There's no reason that these should be evaluated different just because one the two is assigned to a variable...

What's more, the double-splat operator's adoption isn't super widespread, largely because of the preference for the opts={} idiom described above. Also this isn't a use case many would run into in practice. A programmer would have to explicitly pass the empty hash to such a method when they could (regardless of whether kwargs or opts) just omit the argument. So yeah... I think this might be fluke of the interpreter.

Glyoko
  • 2,071
  • 1
  • 14
  • 28
  • I'm still confused because if instead of declaring kwargs as a variable, you just pass an ampty hash literal `**{}`, no argument is passed. Also, no way to pass a method? – Asone Tuhid Mar 26 '17 at 05:55
  • Methods–at least methods as you usually define them in the normal course of coding–are not first class objects in Ruby. So you cannot directly pass those to methods. However, you can use [blocks](https://stackoverflow.com/questions/4911353/best-explanation-of-ruby-blocks) for this, and this is the canonical way of doing things in Ruby. – Glyoko Mar 26 '17 at 06:00
  • Yeah, I'm trying to get my head around those (coming from Python it seems uselessly inconvenient). Any ideas on why `**{}` just disappears from existence? – Asone Tuhid Mar 26 '17 at 08:43
  • Like I said, it doesn't make sense that it disappears like that. `**kwargs` is behaving _correctly_ when it raises an error when you try to pass it to `my_method`. I really think it's a fluke of the interpreter for `**{}` to behave any differently than `**kwargs`. – Glyoko Mar 27 '17 at 18:57
  • According to the linked issue, it is in fact a bug. If your goal in all this is just to learn Ruby conventions and standards, then I wouldn't worry about this too much. Directly passing `**{}` to a method is something you will virtually never do outside of debugging, since passing "nothing" is the default anyway. The rule in Ruby is that "Keywords", at least as they work in Python, are just regular arguments that happen to be hashes. In my example above, `opt1` and `opt2` are both part of the `opts` argument, but when passed to the method, it almost reads as though they are separate variables. – Glyoko Mar 27 '17 at 19:11
  • Thanks. BTW, i just noticed that passing `{}` also results in zero arguments so it's most definitely a bug and passing an empty opts argument could come up much more often than `**{}` – Asone Tuhid Mar 29 '17 at 01:15