Reputation: 31
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
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