[newby] class variables and class instance variable?

Hello.

class Test
   @a = "value"

   def self.a
     @a
   end

   def initialize
     @@a = "value2"
   end

   def a
     @@a
   end
end

puts Test.a # output: value
puts Test.new.a # output: value2

I don't understand (and I'm quite surprised), what is the difference in terms of OO design between class variables, the @@a in the example above, and class instance variables, the @a in the example?

Thanks in advance,
Lionel Thiry

Lionel Thiry wrote:

class Test
  @a = "value"

  def self.a
    @a
  end

  def initialize
    @@a = "value2"
  end

  def a
    @@a
  end
end

puts Test.a # output: value
puts Test.new.a # output: value2

I don't understand (and I'm quite surprised), what is the difference in terms of OO design between class variables, the @@a in the example above, and class instance variables, the @a in the example?

Currently class variables are also shared between different classes that are part of the same inheritance tree. IMHO this is a rarely needed feature and you're better off using regular instance variables on the class (and referring to them via self.class.var from an instance).

Florian Gross wrote:

Lionel Thiry wrote:

I don't understand (and I'm quite surprised), what is the difference in terms of OO design between class variables, the @@a in the example above, and class instance variables, the @a in the example?

Currently class variables are also shared between different classes that are part of the same inheritance tree.

I ignored that. *doing some testing* My! You're right.

IMHO this is a rarely needed feature and you're better off using regular instance variables on the class (and referring to them via self.class.var from an instance).

I totally agree.

Thanks a lot,
Lionel Thiry

Wolfgang Nádasi-Donner wrote:

The class variable ("@@") belongs to the class object, while the instance
variable (@) belongs to the instanciated object. A short example. May be you
want to count how many objects are created based on a class. If you want to
avoid global variables ($), which is a very good decision, you can use class
variables. Using instance variables will not work.

It will. Note that the poster said "class instance variable" which is the instance variable of a Class. Classes are instances of Class and Class inherits from Module which inherits from Object so classes are objects as well:

class Foo
   @counter = 0
   class << self
     attr_accessor :counter
   end

   def initialize()
     self.class.counter += 1
   end
end

Foo.counter # => 0
5.times { Foo.new }
Foo.counter # => 5

Example >>>>>

class Mytest
@@n_of_Mytest = 0

svg% cat b.rb
#!/usr/local/bin/ruby
class Mytest
   @n_of_Mytest = 0

   def initialize
      self.class.instance_eval { @n_of_Mytest += 1}
   end

   def Mytest.n_of_Mytest
      @n_of_Mytest
   end
end

puts Mytest.n_of_Mytest
a = Mytest.new
puts Mytest.n_of_Mytest
b = Mytest.new
c = Mytest.new
puts Mytest.n_of_Mytest
d = Mytest.new
e = Mytest.new
f = Mytest.new
puts Mytest.n_of_Mytest
svg%

svg% b.rb
0
1
3
6
svg%

···

--

Guy Decoux

Example >>>>>

class Mytest
@@n_of_Mytest = 0
def initialize
  @@n_of_Mytest += 1
end
def Mytest.n_of_Mytest
  @@n_of_Mytest
end
end

puts Mytest.n_of_Mytest
a = Mytest.new
puts Mytest.n_of_Mytest
b = Mytest.new
c = Mytest.new
puts Mytest.n_of_Mytest
d = Mytest.new
e = Mytest.new
f = Mytest.new
puts Mytest.n_of_Mytest

Output >>>>>

0
1
3
6

End of Example >>>>>

O.K.?

I've tested your code and I've been surprised that it actually worked. If I correctly understand the mechanism, it's like the class object and its instances are able to access class variables through '@@'. Undubitably, I know now why I couldn't get along with that feature.

Honestly, I largely prefer to think of classes as objects and use the class instance variables when I want to share variables between instances. Using '@@' doesn't seem reliable for me, as IMHO it violates some important OO principles, the kind that if not followed leads to very embarrassing problems.

Thanks for your help,
Lionel Thiry

Output >>>

0 cats, 0 dogs, 0 animals
a new dog
a new dog
2 cats, 2 dogs, 2 animals
a new cat
a new cat
a new cat
5 cats, 5 dogs, 5 animals

End of Example >>>

With this version of ruby

uln% ruby -v
ruby 1.9.0 (2005-03-14) [x86_64-linux]
uln%

The result is

0 cats, 0 dogs, 0 animals
a new dog
a new dog
0 cats, 2 dogs, 0 animals
a new cat
a new cat
a new cat
3 cats, 2 dogs, 0 animals

···

--

Guy Decoux

ts wrote:

With this version of ruby

uln% ruby -v
ruby 1.9.0 (2005-03-14) [x86_64-linux]
uln%

The result is

0 cats, 0 dogs, 0 animals
a new dog
0 cats, 2 dogs, 0 animals
a new cat
3 cats, 2 dogs, 0 animals

So the behavior has been changed to lookup by surrounding module/class on method definition time?

Hello again!

I've refactored a bit the code of Wolfgang. Now, it do a lot of reuse through inheretance. I'm really curious to know what this refactored code would give under ruby 1.9.0. Would you mind to test it for me, please?

Here it is:

class Animal
  @@born = 0
  def initialize
   @@born += 1
   self.class.anew
  end
  def self.anew
    puts "a new animal"
  end
  def self.born
   @@born
  end
end

class Dog<Animal
  def self.anew
    puts "a new dog"
  end
end

class Cat<Animal
  def self.anew
    puts "a new cat"
  end
end

print "#{Cat.born} cats, #{Dog.born} dogs, #{Animal.born} animals\n"
2.times{Dog.new}
print "#{Cat.born} cats, #{Dog.born} dogs, #{Animal.born} animals\n"
3.times{Cat.new}
print "#{Cat.born} cats, #{Dog.born} dogs, #{Animal.born} animals\n"

It still gives the same output:
0 cats, 0 dogs, 0 animals

a new dog

a new dog

2 cats, 2 dogs, 2 animals

a new cat

a new cat

a new cat

5 cats, 5 dogs, 5 animals

Thanks in advance,
Lionel Thiry

ts wrote:

···

"W" == Wolfgang Nádasi-Donner <wonado@donnerweb.de> writes:

Output >>>

> 0 cats, 0 dogs, 0 animals
> a new dog
> 2 cats, 2 dogs, 2 animals
> a new cat
> 5 cats, 5 dogs, 5 animals

End of Example >>>

With this version of ruby

uln% ruby -v
ruby 1.9.0 (2005-03-14) [x86_64-linux]
uln%

The result is

0 cats, 0 dogs, 0 animals
a new dog
0 cats, 2 dogs, 0 animals
a new cat
3 cats, 2 dogs, 0 animals

(Sorry for the top posting.)

I'd like to take a shot at answering Lionel's original question (what is the
difference between class and instance variables). It seems convenient to use
the result of Guy Decoux's example as an example.

If you use class and instance variables "properly", you can achieve the
following result, which can be very useful in some cases. Note that instance
variables in the dog and cat class count the dogs and cats respectively,
while the class variable (in the animal class) counts the animals (i.e., dogs
+ cats):

0 cats, 0 dogs, 0 animals
a new dog
a new dog
0 cats, 2 dogs, 2 animals
a new cat
a new cat
a new cat
3 cats, 2 dogs, 5 animals

I am a newbie to Ruby, my answer is based on what I understand should happen
with instance and class variables.

regards,
Randy Kramer

···

On Tuesday 15 March 2005 08:58 am, ts wrote:

0 cats, 0 dogs, 0 animals
a new dog
a new dog
0 cats, 2 dogs, 0 animals
a new cat
a new cat
a new cat
3 cats, 2 dogs, 0 animals

It still gives the same output:
0 cats, 0 dogs, 0 animals
a new dog
a new dog
2 cats, 2 dogs, 2 animals
a new cat
a new cat
a new cat
5 cats, 5 dogs, 5 animals

with 1.9.0

0 cats, 0 dogs, 0 animals
a new dog
a new dog
2 cats, 2 dogs, 2 animals
a new cat
a new cat
a new cat
5 cats, 5 dogs, 5 animals

···

--

Guy Decoux

Oops, I need to correct myself:

If you use class and instance variables "properly", you can achieve the
following result, which can be very useful in some cases. Note that
instance variables in the dog and cat class count the dogs and cats

In the previous line, it would have been more clear (and correct :wink: to say
"in the dog and cat *instances* (of the animal class)"

respectively, while the class variable (in the animal class) counts the
animals (i.e., dogs + cats):

Randy Kramer

···

On Tuesday 15 March 2005 10:39 am, you wrote:

Randy Kramer wrote:

(Sorry for the top posting.)

I'd like to take a shot at answering Lionel's original question (what is the difference between class and instance variables).

Oops, there is some confusion here: "class variable", "class instance variable" and "instance variable" are three different things.

@a in the context of instance code is an instance variable. "instance variable" is contained in an object, any object. As being internal to the object, it cannot be accessed from outside without a method call.

Code example:
class MyClass
  def initialize # instance method
   # instance code
   @a = "value"
  end
  def a
   @a
  end
end
puts MyClass.new.a # output: value

@a in the context of class code is a "class instance variable". "class instance variable" is contained in an object, but not any one, it is contained in a class, as classes are objects too. Then, as being internal to the class object, it cannot be accessed from outside without a class method call.

Code example:
class MyClass
  #class code
  @a = "value"
  def self.a # class method
   # class code
   @a
  end
  def a # instance method
   # instance code
   @a
  end
end
puts MyClass.a # output: value
puts MyClass.new.a # output: nil

@@a whatever the context is a "class variable". "class variable" is "contained" in a class as if it was a "class instance variable" but without being one, it exists in "another world". In practical terms, the main difference is that '@@' is reachable from any instance without passing through any method call (which IMHO is poor OO design).

Code example:
class MyClass
  # class code
  @@a = 0
  def incr # instance method
   # instance code
   @@a += 1
  end
  def a # instance method
   # instance code
   @@a
  end
  def self.incr # class method
   # class code
   @@a += 1
  end
  def self.a # class method
   # class code
   @@a
  end
end
my_instance = MyClass.new
other_instance = MyClass.new
puts MyClass.a # output: 0
puts my_instance.a # output: 0
puts other_instance.a # output: 0
my_instance.incr
puts MyClass.a # output: 1
puts my_instance.a # output: 1
puts other_instance.a # output: 1
other_instance.incr
puts MyClass.a # output: 2
puts my_instance.a # output: 2
puts other_instance.a # output: 2
MyClass.incr
puts MyClass.a # output: 3
puts my_instance.a # output: 3
puts other_instance.a # output: 3

Is it ok?

Now, there is worse. @@a var is shared not only between the class and and its instances, but also with all inheriting classes and all their instances!

Guy Decoux showed things are different in 1.9.0, but I don't understand how it works.

···

--
Lionel Thiry

ts wrote:

"L" == Lionel Thiry <lthiryidontwantspam@skynetnospam.be> writes:

> It still gives the same output:
> 0 cats, 0 dogs, 0 animals
> a new dog
> 2 cats, 2 dogs, 2 animals
> a new cat
> 5 cats, 5 dogs, 5 animals

with 1.9.0

0 cats, 0 dogs, 0 animals
a new dog
2 cats, 2 dogs, 2 animals
a new cat
5 cats, 5 dogs, 5 animals

Thanks a lot! :slight_smile:

I expected it would give an error or the same output as the original code when executed in ruby 1.9.0.

Would those modifications "correct" its behavior? (I hope I'm not too annoying asking to test code for me)

class Dog<Animal
  @@born = 0
  def self.anew
    puts "a new dog"
  end
end

class Cat<Animal
  @@born = 0
  def self.anew
    puts "a new cat"
  end
end

Thanks in advance,
Lionel Thiry

Ok, thanks! I think I've got the general idea--a class variable and class
instance variable are almost the same, except the class variable is sort of a
"renegade" global type of thing.

Any more than that I'll probably have to let develop as this stuff soaks in a
little more. :wink:

Randy Kramer

---<good stuff snipped>---

···

On Tuesday 15 March 2005 11:44 am, Lionel Thiry wrote:

Oops, there is some confusion here: "class variable", "class instance
variable" and "instance variable" are three different things.

Would those modifications "correct" its behavior? (I hope I'm not too
annoying asking to test code for me)

no it does the same

0 cats, 0 dogs, 0 animals
a new dog
a new dog
2 cats, 2 dogs, 2 animals
a new cat
a new cat
a new cat
5 cats, 5 dogs, 5 animals

you are still accessing the class variable in Animal

···

--

Guy Decoux