Submitted by coreywangler (Wed Jan 14 02:01:44 UTC 2004)
Make @classInstanceVariables consistent with @instanceVariables by restricting them to use within defined class methods.
Prevent @instanceVariable initialization bug
1) Having @classInstanceVariables accessible in the class body is not consistent with the rule that "an @instanceVariable can only be used within instance methods" (in this case the instance is a class, and the methods are class methods). Note that the class body is not a callable class method -- it only runs once, and there is no way to execute that code again.
2) Allowing initialization of @classInstanceVariables in the class body can confuse the programmer, especially where the nubie mistakenly puts an initialization of an @instanceVariable outside of an instance method (making it a @classInstanceVariable instead of what they assumed would be a normal @instanceMethod).
class Foo
@a = 123 # (1) class instance var initialized def foo p @a # (2) 'normal' instance var accessed ...nil not 123 end
end
Disallow @classInstanceVariables in class body.
If wanting to initialize @classInstanceVars from the class body, a class method can be called there (as a class body statement), with the advantage of having the initializing method callable using "super" in a derived class:
class Foo def Foo.init(valA) @classInstanceVarA = valA end Foo.init(400) def Foo.test puts "in Foo.test, @classInstanceVarA is " + @classInstanceVarA.to_s end Foo.test end #==> in Foo.test, @classInstanceVarA is 400 class Bar < Foo def Bar.init(valB) super(800) @classInstanceVarB = valB end Bar.init(864) def Bar.test puts "in Bar.test, @classInstanceVarA is " + @classInstanceVarA.to_s puts "in Bar.test, @classInstanceVarB is " + @classInstanceVarB.to_s end Bar.test end #==> in Bar.test, @classInstanceVarA is 800 #==> in Bar.test, @classInstanceVarB is 864
Using class methods to initialize @classInstanceVars is also useful when creating a derived class using "Class.new(ASuperClass)" as the following example shows:
AnotherBar = Class.new(Bar) AnotherBar.test #==> in Bar.test, @classInstanceVarA is #==> in Bar.test, @classInstanceVarB is AnotherBar.init(888) AnotherBar.test #==> in Bar.test, @classInstanceVarA is 800 #==> in Bar.test, @classInstanceVarB is 888
Note that immediately after "Class.new(ASuperClass)" is used to create a class instance, all @classInstanceVars are nil. This is the case regardless of where initialization of @classInstanceVars is done, because there is no automatic calling of any initialization code for generated class instances (the body of ASuperClass is not executed again). Here it is *necessary* to call a class method to give appropriate values to the @classInstanceVars.
The following shows behaviour that may surprise some (@classInstanceVarA is initialized in the body of class Foo2, but becomes nil in the derived class Bar2 -- because Bar2 is a different class instance)...
class Foo2 @classInstanceVarA = 400 def Foo2.test puts "in Foo2.test, @classInstanceVarA is " + @classInstanceVarA.to_s end Foo2.test end #==> in Foo2.test, @classInstanceVarA is 400 class Bar2 < Foo2 @classInstanceVarB = 864 def Bar2.test puts "in Bar2.test, @classInstanceVarA is " + @classInstanceVarA.to_s puts "in Bar2.test, @classInstanceVarB is " + @classInstanceVarB.to_s end Bar2.test end #==> in Bar2.test, @classInstanceVarA is #==> in Bar2.test, @classInstanceVarB is 864
Disallowing @classInstanceVariables in the class body forces all initialization of @classInstanceVars to be done in a consistent manner, thus avoiding the "surprise" that @classInstanceVars are nil for derived classes.
###--- extract from RCR 185 ---
Maybe warnings to the instance variable assignments in the class body might solve the problem very nicely without modifying language syntax.
- matz.
###----------------------------
...it seems Matz always has the best solutions! :)
Class methods must be defined to access and use @classInstanceVariables, so mandating that they need to be initialized within such defined class methods is not a burden to the programmer.
In my opinion, forcing @classInstanceVariable initialization into class methods makes the code clearer and easier to understand. It also produces code that is more readily extended (code in the class body cannot be re-used).
It makes it clear that: "@instanceVariables are used inside instance methods"
...less surprise ;P
***Advantages***
Makes the use of @classInstanceVar and @instanceVar consistent and clear (even to beginners).
A class method needs to be called to initialize @classInstanceVariables -- this is an advantage because there is no way to execute the statements in the class body again. Having the initialization done in a class method allows that initialization routine to be executed in an inherited class by calling super in the corresponding class method, or by directly calling an inherited class method.
Prevents the @instanceVariable initialization bug by disallowing @instanceVariables from the class body -- a place where their meaning is not so clear to beginners (assumed to be a @instanceVariable, but is actually a @classInstanceVariable).
***Disadvantages***
Programmer might have to write more code, as they must put @classInstanceVariable initialization into class method(s).
In first stage of implementation, give a warning if @instanceVariable is used in the class body.
In second stage, prohibit @classInstanceVariable from class body.
class Foo @variable = 0 #produce an error message end #==> ERROR: instance variables are not allowed in class body
Comments | Current voting | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
|
RCRchive copyright © David Alan Black, 2003-2005.
Powered by .
David Black:
The problem here is that there's no problem: that is, there's no rule saying that instance variables are only visible in instance methods. An instance variable belongs to whatever object is 'self' at the time the instance variable is used. This may or may not be happening inside one of that object's instance methods. It can also happen in an instance_eval. And since 'self' is set to the Class object at the top level of a class definition, instance variables used at that level belong to that object.
I know that people sometimes misunderstand and think they're using a Thing object's instance variable when they're actually using one of Thing's instance variables. But the way it actually works is consistent, and the language mustn't be made inconsistent just to save people having to learn how it works. As a non-beginner, I would not want to have to work around an anomaly that made it otherwise -- nor would I want to have to explain to a beginner why there was this exception to the rule about instance variables :-)
(I definitely wouldn't want a warning either, unless it was only turned on by a specific switch that I could avoid using.)
As with so many things, this boils down to the "a class is just an object" principle. That's a basic design principle of Ruby, and I feel strongly that people should be encouraged to embrace it and accept it completely. This includes the fact that a subclass is not the same object as the class it subclasses, so there's no reason to expect them to share instance variables. (Indeed, no two objects share instance variables, which is why they're called instance variables :-)
Giving warning might help finding problems. The point is after understanding the principles of instance variable in Ruby, whether one can make such mistakes so often, so that language should help and encourage something about it.
I doubt it. And even if it proved to be true, I will not prohibit the so-called "class instance variables" in the class body, warning is good enough.
-matz.
David Black:
But there's nothing wrong with referring to a class's instance variables in the class body; it's actually a good way to maintain state per-class. A warning would definitely label it as a bad practice. I'm afraid it would further discourage people from understanding the underlying design.
Withdrawing RCR
I think keeping @classInstanceVars out of the class body is "good practice" -- avoids pitfalls/confusion, and produces code that is more readily extended.
But I have to agree with all of David's insights into this area, so @classInstanceVars in the class body is not "bad practice", and a warning is thus not appropriate.
Cheers, Corey.