Forum: Ruby Class-tree variables

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
1cf2f33b3f8d9264e2f0cf96067a7a28?d=identicon&s=25 oinkoink (Guest)
on 2007-02-08 21:27
(Received via mailing list)
There's been a lot of blog traffic lately about class variables vs.
class instance variables.
I'd like to give an example of something in-between: class-tree
variables.  These are sort
of like class variables, but they only point downward in the
inheritance tree rather than
up.  That is, each class in an inheritance tree can get a variable
which refers to itself and
all its descendents.  Class-tree variables are implemented by class
variables, but the
implementation occurs only in the top (root) class and is inherited by
the rest of the tree.
It is probably unclear what the heck I am talking about, but the
following example should
make things clear.

# class_tree_var.rb: demo program of class-tree variables
#-- Hey Emacs! Use -*- mode: ruby; coding: utf-8-unix; -*-
class Person
  attr_accessor :name

  class << self
    attr_writer :instances
    def instances
      @instances ||= []
    end
  end

  def initialize( name )
    klass = self.class
    until klass == Person
      klass.instances << self
      klass = klass.superclass
    end
    Person.instances << self
    @name = name
  end

end

class Scholar < Person
end
class Student < Scholar
end
class Professor < Scholar
end
%w(Fred Barney).each{|caveman| Person.new(caveman)}
%w(Wilma Betty).each{|cavewoman| Scholar.new(cavewoman)}
%w(Twerply Bob Sheila).each{|brat| Student.new(brat)}
%w(Whiplash Grindemdown).each{|sadist| Professor.new(sadist)}
puts "Persons are " + Person.instances.collect{|person|
person.name}.join(', ')
puts "Scholars are " +  Scholar.instances.collect{|scholar|
scholar.name}.join(', ')
puts "Students are " + Student.instances.collect{|student|
student.name}.join(', ')
puts "Professors are " + Professor.instances.collect{|professor|
professor.name}.join(', ')
# end of file

$ ruby class_tree_var.rb
Persons are Fred, Barney, Wilma, Betty, Twerply, Bob, Sheila,
Whiplash, Grindemdown
Scholars are Wilma, Betty, Twerply, Bob, Sheila, Whiplash, Grindemdown
Students are Twerply, Bob, Sheila
Professors are Whiplash, Grindemdown

I don't actually recommend this.  I think that in Ruby it's better not
to use inheritance for
such things; you have an abundance of alternatives that are more
flexible.  Functionality
built into inheritance tends to break upon refactoring.  But I think
this example does shed
some light on what class instance variables are capable of.  It did
for me, anyway.

Regards, Bret
Bret Jolly
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-02-08 21:42
(Received via mailing list)
Hi --

On Fri, 9 Feb 2007, oinkoink wrote:

> There's been a lot of blog traffic lately about class variables vs.
> class instance variables.  I'd like to give an example of something
> in-between: class-tree variables.  These are sort of like class
> variables, but they only point downward in the inheritance tree
> rather than up.  That is, each class in an inheritance tree can get
> a variable which refers to itself and all its descendents.
> Class-tree variables are implemented by class variables, but the
> implementation occurs only in the top (root) class and is inherited
> by the rest of the tree.

I think that's a typo; I don't see any class variables in your code.

>    def instances
>    Person.instances << self
> end
> puts "Professors are " + Professor.instances.collect{|professor|
> I don't actually recommend this.  I think that in Ruby it's better
> not to use inheritance for such things; you have an abundance of
> alternatives that are more flexible.  Functionality built into
> inheritance tends to break upon refactoring.  But I think this
> example does shed some light on what class instance variables are
> capable of.  It did for me, anyway.

It's a good illustration of the fact that instance variables are the
way to maintain state per object, and that that's the same for class
objects as for other objects.


David
1cf2f33b3f8d9264e2f0cf96067a7a28?d=identicon&s=25 oinkoink (Guest)
on 2007-02-08 21:51
(Received via mailing list)
On Feb 8, 12:41 pm, dbl...@wobblini.net wrote:
> > by the rest of the tree.
> I think that's a typo; I don't see any class variables in your code.

Yes, sorry.  I meant "class instance variables" (and also
"descendants") but the
word "instance" got lost in careless editing.
E7559e558ececa67c40f452483b9ac8c?d=identicon&s=25 Gary Wright (Guest)
on 2007-02-08 22:14
(Received via mailing list)
On Feb 8, 2007, at 3:25 PM, oinkoink wrote:
> That is, each class in an inheritance tree can get a variable
> which refers to itself and
> all its descendents.  Class-tree variables are implemented by class
> variables, but the
> implementation occurs only in the top (root) class and is inherited by
> the rest of the tree.

Ruby's class variables already have this behavior, sort of.
The gotcha is that order matters.  More info after the example...

class A; end
class B < A; end

class A; @@alpha = 42; end     # establishes @@alpha for A and
descendants
class B; @@alpha = 52; end     # reuses @@alpha from A
class A; @@alpha; end    # 52
class B; @@alpha; end    # 52

#################

class C; end
class D < C; end
class E < C; end
class D; @@beta = 42; end      # establishes @@beta for B and
descendants
class C; @@beta = 52; end      # establishes @@beta for A and
descendents
                                # *excluding* B since that branch
already
                                # has @@beta defined!
class C; @@beta; end    # 52
class D; @@beta; end    # 42
class E; @@beta; end    # 52


There are three issues that make Ruby's class variables obtuse:

1) The inheritance tree lookup procedure described above.
2) The lexical scoping used to initiate the lookup (see below).
3) The name 'class variable', which has a much different meaning
    in Ruby than in other languages because of 1) and 2)

Lexical Scoping

The starting class for class variable name resolution (step 1) is
determined lexically (e.g., by the parser) not dynamically:

$ cat lexical.rb
class A
   @@alpha = 42   # lexically within A, @@alpha anchored to A
end

@@alpha = 52     # lexically in top-level, @@alpha anchored to Object

p (class Object; @@alpha; end)   # 52
p (class A; @@alpha; end)        # 42

p A.class_eval { @@alpha }       # 52!  lexically at top-level, not A!

def A.show_alpha
   @@alpha                        # lexically at top-level!!!
end

p A.show_alpha                   #52!
$ ruby lexical.rb
52
42
52
52

########################################


$ cat lexical2.rb

@@alpha = 52     # lexically in top-level, @@alpha anchored to Object

class A
   @@alpha = 42   # lexically within A, @@alpha anchored to A
                  # *but* @@alpha already defined in ancestor (Object)
                  # so @@alpha from Object is referenced
end


p (class Object; @@alpha; end)   # 42
p (class A; @@alpha; end)        # 42
$ ruby lexical2.rb
42
42



Gary Wright
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-02-08 22:34
(Received via mailing list)
Hi --

On Fri, 9 Feb 2007, Gary Wright wrote:

> The gotcha is that order matters.  More info after the example...
Also you wouldn't want to use class variables to maintain state for a
class, since they're considered the property equally of all the
instances.

> class C; end
> class D < C; end
> class E < C; end
> class D; @@beta = 42; end      # establishes @@beta for B and descendants
> class C; @@beta = 52; end      # establishes @@beta for A and descendents
>                              # *excluding* B since that branch already
>                              # has @@beta defined!

I think there's an error here; C and D aren't related to A and B.

>  in Ruby than in other languages because of 1) and 2)
4) The @@ prefix, which leads people to think there must be
      some similarity or kinship with instance variables.  Class
      variables would be better off using $$, because they're more
      closely related to globals than to instance variables.

> @@alpha = 52     # lexically in top-level, @@alpha anchored to Object
>
> p (class Object; @@alpha; end)   # 52
> p (class A; @@alpha; end)        # 42
>
> p A.class_eval { @@alpha }       # 52!  lexically at top-level, not A!

I think the scope of class_eval is pretty much flat with the scope
it's created in, even for constants and class variables.

> def A.show_alpha
> @@alpha                        # lexically at top-level!!!
> end
> p A.show_alpha                   #52!

I'm not sure whether it's lexical scoping or just the fact that A's
singleton class is sharing @@alpha with Object.  If you do:

   class String
     p @@alpha
   end

you'll also get 52.  Also, if you do:

   class << A
     @@alpha = 1
   end

   p @@alpha

Object's @@alpha will be 1.


David
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2007-02-08 22:51
(Received via mailing list)
oinkoink wrote:
> implementation occurs only in the top (root) class and is inherited by
> the rest of the tree.
...

This is a solution to a slightly different problem (inheritance of
hashes of attributes), but it might be interesting anyway:

http://redshift.sourceforge.net/superhash/

A little example:

require 'superhash'

class A
   class_superhash :options

   options[:foo] = "A foo"
   options[:bar] = "A bar"

   def options; self.class.options; end
end

class B < A
   options[:foo] = "B foo"
end

p A.options
p B.options.to_hash
p B.new.options.to_hash

__END__

output:

{:foo=>"A foo", :bar=>"A bar"}
{:foo=>"B foo", :bar=>"A bar"}
{:foo=>"B foo", :bar=>"A bar"}
E7559e558ececa67c40f452483b9ac8c?d=identicon&s=25 Gary Wright (Guest)
on 2007-02-08 23:33
(Received via mailing list)
On Feb 8, 2007, at 4:33 PM, dblack@wobblini.net wrote:
>
> I think there's an error here; C and D aren't related to A and B.

My bad, pay attention to the code, not the comments.  Comments should
have had D for B and C for A.

>     p @@alpha
>   end
> you'll also get 52.

@@alpha has already been established in Object, which
takes precedence over anything in String (since it was
defined first).  Consider:

irb(main):001:0> class A; end
=> nil
irb(main):002:0> class B
irb(main):003:1>   def A.foo
irb(main):004:2>     @@alpha
irb(main):005:2>   end
irb(main):006:1>   @@alpha = 'B scope'
irb(main):007:1> end
=> "B scope"
irb(main):008:0> A.foo
=> "B scope"

In this example the definition of A::foo is lexically within the
(class B;end) block and so @@alpha is resolved relative to B. Not to
the top-level or relative to B's singleton class.

> Also, if you do:
>
>   class << A
>     @@alpha = 1
>   end
>
>   p @@alpha
>
> Object's @@alpha will be 1.

Well it turns out that the scope created by "class <<A; end" is not
at all like the scope created by "class A; end", at least with respect
to class variables.  Throw away all the stuff above and consider fresh:

class A
   @@alpha = 1
end

p (class <<A; @@alpha; end)    # warning, top-level scope for @@alpha


In fact, I couldn't figure out how to even create a class variable
that was anchored to the singleton class of a class.  I didn't try too
hard though...

Gary Wright
This topic is locked and can not be replied to.