Class Level inheritable attributes - are we there yet?

D

dreamcat four

Hi,
I'm having trouble understanding why this isn't available from the Ruby core?
Does core not provide an alternative / satisfactory implementation of this?

# clia.rb - Class Level Inheritable Attributes

module ClassLevelInheritableAttributes
def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def inheritable_attributes(*args)
@inheritable_attributes ||= [:inheritable_attributes]
@inheritable_attributes += args
args.each do |arg|
class_eval %(
class << self; attr_accessor :#{arg} end
)
end
@inheritable_attributes
end

def inherited(subclass)
@inheritable_attributes.each do |inheritable_attribute|
instance_var = "@#{inheritable_attribute}"
subclass.instance_variable_set(instance_var,
instance_variable_get(instance_var))
end
end
end
end

class Polygon
include ClassLevelInheritableAttributes
inheritable_attributes :sides
@sides = 8
end

puts Polygon.sides # => 8

class Octogon < Polygon; end

puts Polygon.sides # => 8
puts Octogon.sides # => 8

Ref: http://railstips.org/2006/11/18/class-and-instance-variables-in-ruby

Its rather difficult to believe we must in each case define / require
our own custom module. Please note - i'm not complaining here. I just
want to know whether the above example is "the best solution in use"
for 1.8... 1.9....


Best regards,

dreamcat4
(e-mail address removed)
 
D

David A. Black

Hi --

Hi,
I'm having trouble understanding why this isn't available from the Ruby core?
Does core not provide an alternative / satisfactory implementation of this?

# clia.rb - Class Level Inheritable Attributes

module ClassLevelInheritableAttributes
def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def inheritable_attributes(*args)
@inheritable_attributes ||= [:inheritable_attributes]
@inheritable_attributes += args
args.each do |arg|
class_eval %(
class << self; attr_accessor :#{arg} end
)
end
@inheritable_attributes
end

def inherited(subclass)
@inheritable_attributes.each do |inheritable_attribute|
instance_var = "@#{inheritable_attribute}"
subclass.instance_variable_set(instance_var,
instance_variable_get(instance_var))
end
end
end
end

class Polygon
include ClassLevelInheritableAttributes
inheritable_attributes :sides
@sides = 8
end

puts Polygon.sides # => 8

class Octogon < Polygon; end

puts Polygon.sides # => 8
puts Octogon.sides # => 8

Ref: http://railstips.org/2006/11/18/class-and-instance-variables-in-ruby

Its rather difficult to believe we must in each case define / require
our own custom module. Please note - i'm not complaining here. I just
want to know whether the above example is "the best solution in use"
for 1.8... 1.9....

I would probably write it a little differently, maybe like this:

module ClassLevelInheritableAttributes
def inheritable_attributes(*args)
(class << self; self; end).class_eval do
attr_accessor *args
end

@inheritable_attributes ||= []
@inheritable_attributes.concat(args)
end

def inherited(subclass)
@inheritable_attributes.each do |attr|
subclass.send("#{attr}=", send(attr))
end
super
end
end

class Whatever
extend ClassLevelInheritableAttributes
end

etc. I'm not sure it's a common enough idiom to be worthy of a new
language construct, though. I love the fact that you can make what are
essentially language-level-like constructs from a few lines of code in
Ruby. I think that very, very few of them should be promoted to the
actual language.


David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)
 
D

dreamcat four

The problem with class variables in Ruby, is that a class variable is
the same object across all subclasses of a class. This is in my
opinion almost never what you=E2=80=99d want. More probable is that you=E2=
=80=99d want
the individual class instance to have an accessor. (Remember classes
are objects in Ruby).

dreamcat4
(e-mail address removed)
 
D

dreamcat four

Ah,
I seems that ActiveSupport=E2=80=99s class_inheritable_accessor, seems to d=
o
the same. class_inheritable_accessor (or what I showed) uses instance
variables (@foo) at the Class instance level. These variables are not
shared across all subclasses.

require 'activesupport'

class Base
class_inheritable_accessor :value
self.value =3D '123'
end

class Derived < Base; end
class Derived2 < Base; end

Derived.value #=3D> '123'
Derived.value =3D 'abc'
Base.value #=3D> '123'
Derived2.value #=3D> '123


It becomes a problem when using a mixture of libraries which all
define methods on Class, Kernel or Object. Obviously namespace clashes
can happen, (and indeed they have already).


Best regards,

dreamcat4
(e-mail address removed)
 
D

dreamcat four

Oh, there are lots of problems with class variables :) I didn't
mention them. Was there something in my post that made it sound like I
was suggesting class variables?

No, its just a generally accepted problem with the ruby language.

dreamcat4
(e-mail address removed)
 
R

Robert Klemme

The problem with class variables in Ruby, is that a class variable is
the same object across all subclasses of a class. This is in my
opinion almost never what you’d want. More probable is that you’d want
the individual class instance to have an accessor. (Remember classes
are objects in Ruby).

Class variables are one of the few features I would elect for completely
throwing out of the language since they are complex, can be error prone
(initialization order matters) and use cases are rare (if existent at all).

Having said that I am not sure that you actually need a language
construct when you can do this:


class A
class <<self
def foo
@foo || superclass.foo
end
end

@foo = 1
end

class B < A
end

class C < A
@foo = 2
end

[A,B,C].each do |cl|
printf "%-2s %p\n", cl, cl.foo
end

robert@fussel:/tmp$ ruby1.9 cl.rb
A 1
B 1
C 2

And I completely agree to what David said:
I'm not sure it's a common enough idiom to be worthy of a new
language construct, though. I love the fact that you can make what are
essentially language-level-like constructs from a few lines of code in
Ruby. I think that very, very few of them should be promoted to the
actual language.

Kind regards

robert
 
D

dreamcat four

Having said that I am not sure that you actually need a language construc= t
when you can do this:


class A
=C2=A0class <<self
=C2=A0 =C2=A0def foo
=C2=A0 =C2=A0 =C2=A0@foo || superclass.foo
=C2=A0 =C2=A0end
=C2=A0end

It shouldn't be mandated to re-invent the wheel in any programming
language, even if its fun style / fun to write. Here is why Robert:
because you would have just put 2 bugs into your code.

1) It breaks when you want to set foo =3D nil in a subclass.
2) Not having an accessor definition will break other code.

puts A.instance_variables.inspect
=3D> ["@foo"]
puts B.instance_variables.inspect
=3D> []
puts C.instance_variables.inspect
=3D> ["@foo"]

Most people would try / expect to fix by putting attr_accessor :foo at
top class A. It simply doesn't work and the regular accessor is not
inherited. Because in Ruby the Class is instantiated as an instance of
object. You could probably imagine a little imaginary OO professor
tugging at your clothes and saying 'tut tut, such a shame' or
something. And you might argue back that the point was that these
aren't bugs if you understand the limitations etc, etc. Just like
being back at school eh? But i can assure you when any real programmer
in the real world comes along to use your classes and / or modify your
code, these holes are eventually surface as real breakages / bugs.


Heres my own top 3 recommendations:

* Give up / don't use any. Avoid if possible.

* Use the activeresource class inheritable accessor. I guess that's
not so much of a stretch if you are already depending upon
activeresource for other things.

* Request a real language implementation in ruby core because class is
an object. It would be entirely a matter for the ruby development team
to decide whether they would want to implement it, and/or how. I'm
very surprised no-one has asked for this on 1.9 line, or to change the
existing @@ behaviour since are changed the block level variable
scoping and many other things.


dreamcat4
(e-mail address removed)
 
R

Robert Klemme

2009/10/11 dreamcat four said:
It shouldn't be mandated to re-invent the wheel in any programming
language, even if its fun style / fun to write.

Which wheel did I reinvent?
Here is why Robert:
because you would have just put 2 bugs into your code.

1) It breaks when you want to set foo =3D nil in a subclass.
2) Not having an accessor definition will break other code.

puts A.instance_variables.inspect
=3D> ["@foo"]
puts B.instance_variables.inspect
=3D> []
puts C.instance_variables.inspect
=3D> ["@foo"]

David has said everything there is to say about this. I modeled the
quick hack based on your original example which obviously needed to
maintain constant values at class level which would have been better
done like this:

11:07:12 desktop$ ruby19 cl.rb
A 1
B 1
C 2
11:07:24 desktop$ cat cl.rb
class A
Foo =3D 1
end

class B < A
end

class C < A
Foo =3D 2
end

[A,B,C].each do |cl|
printf "%-2s %p\n", cl, cl::Foo
end

If any, that would have been the "bug" in my approach. The code was
merely meant to demonstrate what can be done with a few lines of code
and that accessors (in fact all methods) defined for class instances
are inherited to subclasses.

Regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
D

Dreamcat Four

Ah,

dreamcat said:
* Use the activeresource class inheritable accessor. I guess that's
not so much of a stretch if you are already depending upon
activeresource for other things.

* Request a real language implementation in ruby core because class is
an object. It would be entirely a matter for the ruby development team
to decide whether they would want to implement it, and/or how. I'm
very surprised no-one has asked for this on 1.9 line, or to change the
existing @@ behaviour since are changed the block level variable
scoping and many other things.

I seem to have been wrong about recommending these
activesupport/class_inheritable_accessor. They are not fully up to
scratch.

require "activesupport"
class Cartoon
self.class_inheritable_accessor :_
self._ = "deputy dawg"

def initialize(*args, &block)
puts self.class._.inspect
puts self._.inspect
self._ = "the lazy cat"
puts self._.inspect
puts self.class._.inspect
end
end
Cartoon.new

=>
"deputy dawg"
"deputy dawg"
"the lazy cat"
"the lazy cat"

As you can see, modifying the value in the instantiated object also
modifies the value back in the class object.


So back to 'clia.rb' like in the first 2 posts. I was maybe going to
suggest that we should make a new accessor method. But digging deeper we
can see activesupport have already defined 'cattr_accessor' aswell (this
time for attributes which aren't inherited and passed on to either
instances or subclasses).

Then i searched for 'ciattr_accessor' in case someone had already made
an inheritable version.
http://atomos.rubyforge.org/svn/atomos/lib/extra.rb Does it do the
required job? No (is another duplication of cattr). Potential namespace
conflict? Yes.


David has shown that we should 'extend' and not define any accessor
method. So I guess its the better choice (since pointing out about the
@=nil). Im definately coming around to understand better and appreciate
more David's solution here. Thank you.


Best regards,

dreamcat4
(e-mail address removed)
 
D

Dreamcat Four

Dreamcat said:
David has shown that we should 'extend' and not define any accessor
method. So I guess its the better choice (since pointing out about the
@=nil). Im definately coming around to understand better and appreciate
more David's solution here. Thank you.

I would change the following line:
- @inheritable_attributes.each do |attr|
+ inheritable_attributes.each do |attr|

Provide the full usage:

class MyObject < Object
extend ClassLevelInheritableAttributes
inheritable_attributes :attr_1, :attr_2, ...
end

Using extend only once and the feature itself will also propagate to all
subclasses.

class MySubclass < MyObject
inheritable_attributes :attr_3, :attr_4, ...
end

This means that you could mix into Object, just still left with possible
namespace conflict on 1 new method ('inheritable_attributes') as we had
discussed earlier. I don't believe we can do anything about that here.
Only if defined as an official method would everybody know not to use
it.
 
G

Gregory Brown

I'm still not seeing why it's so important that it be addressed in the
core. Of course I'd be glad to see @@ changed to something more useful
(Robert and I have been among the most vocal critics of class
variables over the years :)

Here's my vote for making @@ a syntax error. :)

-greg
 
A

ara.t.howard

Its rather difficult to believe we must in each case define / require
our own custom module. Please note - i'm not complaining here. I just
want to know whether the above example is "the best solution in use"
for 1.8... 1.9....

you certainly aren't complaining and i must say that i'm a little
disappointed in the responses you are getting from people.

let me start out by saying that it is a *fact* that inheritable class
attributes are both useful and re-invented over and over. do a search
on github, rubyforge, raa, google, and this mailing list and you will
find hundreds of examples. github alone has 237 pages of results for
this search in many languages. i'd also point out that there are
really two kinds of rubyists: those who write code for themselves and
those who write code for others. if you systematically do searches
(on the same sources as above) for the people are against permutations
of class state vs. people are for it you will see a trend: people who
write lots of libraries (especially 'dsl-y' libraries) are in favor
of, and make heavy us of, class state of all kinds including
inheritable state - those who are against it are mainly writing their
own code and using other people's libraries. the irony is that *most*
of the popular ruby libraries make use of inheritable class state:
just a short list

. merb
. ramaze
. sqlite-ruby
. main
. couchrest
. rubyigen
. action_mailer
. active_controller
. active_record
. extlib
. facets
. actionwebservice
. active_support
. zen test
. fattr
. etc...

so, if you like any of those libraries and what they have to offer,
you like class inheritable state being managed for you. in addition
i'd point out that anyone who likes esoteric concepts like 'method
dispatch' or 'constant lookup' also likes auto-magically maintained
class state. honestly it's tiresome to see this idea still being
debated on ruby-talk with buggy code and claims that rolling your own
inheritance is easy (it isn't - trust me - at least not in the general
case). therefore i'd for one, as author of dozens of libraries which
have rolled their own class var inheritance, like to offer my support
of your plea: there absolutely should be a 'standard' method of doing
this. i don't know if that means being in core or that it should be a
library (possibly distributed with core), but you are quite correct in
observing that one has to be careful mixing methods with common names
like 'inheritable' into Object etc. a common, simple, and reusable
library would be a great help to many library writers and people that
claim that it wouldn't be obviously aren't writing a lot of libraries
- at least not certain types of libraries.

i'll leave you with my current favorite method of rolling class
inheritable attributes:


the fattr lib, avail as gem and at http://github.com/ahoward/fattr.
btw, anyone that thinks this is *easy* stuff should complete

http://rubyquiz.com/quiz67.html

although some of the complete answers are terse they are *not*
simple. the fattr code is based on the best answers.



cfp:~/src/git/fattr > cat a.rb
require 'rubygems'
require 'fattr'

class A
class << A
fattr:)x, :inheritable => true){ 42 }
end
end

class B < A; end

class C < B; end


p C.x
p B.x
p A.x
puts

B.x = 42.0

p C.x
p B.x
p A.x
puts

C.x! # force re-initialization from parent(s)

p C.x
p B.x
p A.x
puts


class K
end
class L < K
end
module M
fattr:)x, :inheritable => true){ 42 }
end
K.extend(M)

p L.x



cfp:~/src/git/fattr > ruby a.rb
42
42
42

42
42.0
42

42.0
42.0
42

42


regards.
 
G

Gary Wright

i'll leave you with my current favorite method of rolling class
inheritable attributes:


the fattr lib, avail as gem and at http://github.com/ahoward/fattr.
btw, anyone that thinks this is *easy* stuff should complete


I was going to post a pointer to fattr but now I don't have to.

I've used fattr for a few private projects and have found it quite
useful.

Gary Wright
 
D

David A. Black

you certainly aren't complaining and i must say that i'm a little
disappointed in the responses you are getting from people.

let me start out by saying that it is a *fact* that inheritable class
attributes are both useful and re-invented over and over. do a search
on github, rubyforge, raa, google, and this mailing list and you will
find hundreds of examples. github alone has 237 pages of results for
this search in many languages. i'd also point out that there are
really two kinds of rubyists: those who write code for themselves and
those who write code for others. if you systematically do searches
(on the same sources as above) for the people are against permutations
of class state vs. people are for it you will see a trend: people who
write lots of libraries (especially 'dsl-y' libraries) are in favor
of, and make heavy us of, class state of all kinds including
inheritable state - those who are against it are mainly writing their
own code and using other people's libraries. the irony is that *most*
of the popular ruby libraries make use of inheritable class state:
just a short list

I'm puzzled by the divisive tone of this. I think we're all on the
same side -- not necessarily of every topic that gets discussed here,
but of the basic principle that we're all trying to help each other.

In any case -- I'm not "against" class state. (I'm not even sure what
that would mean.) I am conservative about use-cases, even important
ones, making their way into the language as language-level constructs.
That's what I've emphasized in this thread.

I think too that you're conflating two distinct sub-issues: the
creation of language-level constructs, and the phenomenon of rewriting
the same code over and over. Obviously the latter is a bad idea, and
no one in this thread is suggesting otherwise. If I say that Ruby can
sustain a lot of quasi-language-feature-ish add-ons without having
them be put in the language, that does not imply that these should be
typed out by hand anew every time they're going to be used.


David

--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)
 
T

trans

Closer to what you want?

class A
def self.foo
instance_variable_defined?("@foo") ? @foo : foo!
end
def self.foo!
@foo = ancestors[1].foo if ancestors[1].respond_to?:)foo)
end
def self.foo=(x)
@foo = x
end
end
 
A

ara.t.howard

that does not imply that these should be
typed out by hand anew every time they're going to be used.

??

the OP simply asked why a useful idiom wasn't supported in core, which
includes it's libraries. he very clearly asked for "the best solution
in use." the subsequent posts responded as to why we shouldn't change
the language, which was never suggested, and a critique of the of the
example code, which was never asked for.

the implication was quite clear: that there isn't a best practice and
that solution should be typed out by hand since "you can make what are
essentially language-level-like constructs from a few lines of code in
Ruby"

this really isn't true in this case and it is precisely at the center
of the OP's dilemma: how to best approach having inheritable class
state without writing subtle and error prone code.

unless a concrete answer regarding which libraries to use or a tested,
proven, and complete implementation is given i think the the OP isn't
being given a fair answer to a fair question which was not: 'what do
people think about this', but was instead: 'how, exactly, should i do
this and follow best practices.'

honestly, if you read the thread start to finish, i do not think the
OP's question was given due consideration and i also think his intent
was co-opted into a request for a language change.

i'm simply requesting that the group focus on a good, concrete,
solution for a hard problem instead of simply telling people that the
issue is so easy that it's not even worth solving.

i'd love to see some thinking about the various attempts at a complete
solution. the OP has already noted that the most common are flawed
for even moderately complex use cases, maybe the collective can narrow
in on, and even improve upon, the better ones. i've personally spent
a week or two trying to solve this problem in the general case and
have found it profoundly slippery to get right. i've already posted
my attempt at a minimal but complete solution (fattr) but would love
to hear some legitimate and thoughtful analysis of other approaches.
if a particular solution could gain some momentum as being best
practice it would a big help for the next generation of ruby
programmers - so much so that an RCR, or whatever they are called
these days, might really be in order.

regards.
 
R

Robert Klemme

2009/10/13 ara.t.howard said:
i'm simply requesting that the group focus on a good, concrete,
solution for a hard problem instead of simply telling people that the
issue is so easy that it's not even worth solving.

Ara, I don't agree to everything you write above especially since - as
far as I can see - "the problem" was never stated precisely. But
let's not follow that chain of discussion.
i'd love to see some thinking about the various attempts at a complete
solution. =A0the OP has already noted that the most common are flawed
for even moderately complex use cases, maybe the collective can narrow
in on, and even improve upon, the better ones. =A0i've personally spent
a week or two trying to solve this problem in the general case and
have found it profoundly slippery to get right. =A0i've already posted
my attempt at a minimal but complete solution (fattr) but would love
to hear some legitimate and thoughtful analysis of other approaches.
if a particular solution could gain some momentum as being best
practice it would a big help for the next generation of ruby
programmers - so much so that an RCR, or whatever they are called
these days, might really be in order.

OK, let's start over. For that first I would like to understand what
the problem actually is that needs to be solved here. There are many
aspects to this and I would love to see something like a requirements
list which particularly states the problem. Questions I have in mind:

- Do those attributes need to change or are they set once (quasi /
real constant)?
- If a subclass inherits a value from the superclass is it supposed to
inherit changes to the value as well?
- What happens if a super class has its attribute unset? You might
want to retain all sub class values - or not.
- What happens if a super class has its attribute set? You might want
to override all sub class values at this moment.
- Must modules along the inheritance chain get their own values or is
the feature in question restricted to class instances? (Note, it's
almost impossible to include modules here because they may be part of
multiple inheritance chains.)
- Is the feature expected to work with different combinations to the
questions above.

Concluding from the complexity you attribute to the problem I assume
you can provide more of those guiding questions. Once we have common
understanding what the problem is we can start to think about
solutions.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,696
Latest member
BarbraOLog

Latest Threads

Top