FireBurn256
FireBurn256

Reputation: 31

Why does Ruby compiler count expected number of arguments in this case as zero?

I am quite new to the Ruby, and I ran into problem with defining an object of a class. If I understand correctly, def initialize should ask for three calues, but the code compiles with an error that three is given, but it expected zero values.

I thought the mistake there would be in attr_accessor, but a quick test showed that this is not a case.

The ruby version is 2.6.4

test.rb:16:in `new': wrong number of arguments (given 3, expected 0) (ArgumentError)

class Battler
  attr_accessor :health, :damage, :armor,
  def initialize(health, damage, armor)
    @health = health
    @damage = damage
    @armor = armor
  end

  def able?
    return @health > 0
  end

end

knight = Battler.new(1000, 10, 3)
goblin = Battler.new(20, 6, 1)

The problem starts with a

knight = Battler.new(1000, 10, 3)

Upvotes: 3

Views: 346

Answers (1)

cremno
cremno

Reputation: 4927

As you may know attr_accessor accepts a list of symbols (or strings) and for each one two methods are defined: a so-called a setter (e.g. health=) and getter (health). A list (not an actual type btw.) is one or several expressions separated by a comma, so in your case Ruby expected another expression after :armor because you put a comma thereafter.

(If the trailing comma was intentional, tell us. Some programmers use and recommend them in certain cases (and depending on the language). Though it would make any answer a bit more complicated since Ruby is actually fine with them - just not in this case.)

However that's only one part of your bug's explanation. One of Ruby's most basic features as a programming language is that nearly everything has a value (expressions). For example, try something like x = if ... in other languages! It often won't work as many languages have if-statements, not if-expressions.

In Ruby method definitions are also expressions: defining a method via def (def foo; end) used to return nil. Since Ruby 2.1 method definitions return the method's name (:foo). Now the return value of def-expressions may not seem to be very useful to you and it isn't in many cases but you probably agree the method name (:foo) is certainly more useful than nil.

The primary use case for this are methods like private, public, method_function, etc. which aren't keywords but are used like one (their defining feature is for calls the parentheses are omitted):

private def foo
end
# before 2.1
private  # from now on every method in this class context will be private
def foo
end
# or
def foo
end
private :foo  # only foo will be private but we have to repeat the name

Our friend attr_accessor is also such a keyword-like method but the value of def expressions is more important here. Remember it's the name of a method as a symbol. You wrote (simplified):

attr_accessor :health, :damage, :armor,
def initialize(health, damage, armor)
end

# which after the method was defined becomes
attr_accessor :health, :damage, :armor, :initialize

To make it a bit more clear - without attr_accessor your code might look like this:

class Battler
  def initialize(health, damage, armor)  # takes 3 required arguments
  end
  def health
    @health
  end
  def health=(v)
    @health = v
  end
  # damage, armor
  def initialize  # takes no arguments
    @initialize
  end
  def initialize=(v)
    @initialize = v
  end
end

You see you're redefining Battler#initialize and your new one doesn't take arguments because getters usually don't.

Redefinitions aren't treated as errors since they can be intentional however Ruby can emit a warning for you. Run ruby -w test.rb on the command line and it outputs:

test.rb:15: warning: assigned but unused variable - knight
test.rb:16: warning: assigned but unused variable - goblin
test.rb:2: warning: method redefined; discarding old initialize
test.rb:3: warning: previous definition of initialize was here
Traceback (most recent call last):
         1: from test.rb:15:in `<main>'
test.rb:15:in `new': wrong number of arguments (given 3, expected 0) (ArgumentError)

Here you can see that in line 2 the initialize method which was originally defined on line 3 is redefined. If you're confused why line 3 comes before line 2 here, then remember that the method definition is part of the list passed to attr_accessor so the def-expr has to be evaluated before the call.

Upvotes: 5

Related Questions