From: ara.t.howard@... Date: 2006-06-02T02:35:26+09:00 Subject: Re: An alternative to the class Foo < Struct.new(vars) idiom and SuperStruct On Fri, 2 Jun 2006, Mauricio Fernandez wrote: > On Fri, Jun 02, 2006 at 12:27:32AM +0900, ara.t.howard@noaa.gov wrote: >> On Thu, 1 Jun 2006, Mauricio Fernandez wrote: >> >> thought i'd just chime in here and show how this would be done with traits >> since it has overlapping aims: >> > [...] > > Please indulge some quick questioning on my part; all the answers are in the > sources but my eyeballs hurt a bit atm. and I'd prefer to avoid reading > traits-0.9.1.rb's ~700 lines of code :-) After a cursory look, it's clear > traits and SuperClass are in different leagues. SuperClass goes for minimalism > and meta-programming self-restraint (no methods added to core classes, no > hooks used...); traits seems to do so much more and pays the cost (I can see a > load of methods/instance variables being added at Object's level, > Object.singleton_method_added, etc.). all true. traits does alot. in particular it gives 'pols' semantics to default values inherited via include or normal inheritence - something which is actually tricky to do in ruby. > >> harp:~ > cat a.rb >> require 'traits' >> class MyClass >> include TraitInit >> trait_initialize 'a', 'b' >> def sum() a + b end >> end > > Would def sum; @a + @b end also work? > Plain instance variables are the main appeal of SuperStruct or SuperClass. yes. >>> Unlike Struct.new, you can use SuperClass to generate classes in the middle >>> of the inheritance chain: >>> > [...] >> harp:~ > cat a.rb >> require 'traits' >> >> class X >> include TraitInit >> trait_initialize >> trait :foo => 1 >> end >> >> class Y < X >> trait :bar, :baz => 10 >> trait(:foo){ baz + 1 } >> def fubar() foo + baz end >> end >> >> p Y.new(:bar => 10, :baz => 1).foo >> p Y.new(:bar => 1).fubar > > Interesting. Can this be made to work if X is defined as > class X > attr_reader :foo > def initialize(foo); @foo = foo end > end > and doesn't include TraitInit, or when it does something non-trivial in > #initialize? In other words, is it possible to have a single class in the > hierarchy use traits' trait_initialize without touching the rest? yes. trait_initialize is just a hook to do this def initialize *argv trait_init *argv end so any class may just use trait_init directly. > And is the block evaluated every time one calls #foo, or only once? only once. you can do trait 't' => 'simple_default_value' or trait('t'){ 'default_deferred_evaluated_in_context_of_self' } > One last question: does trait provide some mechanism to create #==, #hash > and #eql?, and if so, are they static-ish (only considering instance > variables/traits at the moment they were created) or dynamic (taking into > account traits defined later)? one neat thing about traits is that they are remembered dynamically and in the order defined. eg. class C trait 'a' trait 'b' class_trait 'c' end p C.traits #=> [['a', 'a='], ['b', 'b=']] p C.rtraits #=> ['a', 'b'] p C.class_traits #=> [['c', 'c=']] p C.class_rtraits #=> ['c'] so it's trival do do module TraitsEqual def eql other to_hash.eql other.to_hash end def to_hash self.class.rtraits.inject({}){|h,t| h.update t => send(t)} end end > I hesitated about which one would be better, and finally added both to > SuperClass, but maybe there's something for/against one of them. it's totally dynamic in traits - eg i go all the way up the chain and include any traits added later. it is a tough choice. for my use case i wanted this: module Properites 'width' => 42 'height' => 42 end class C include Properties trait('size'){ width * height } # this gets width, height, and size def to_hash self.class.rtraits.inject({}){|h,t| h.update t => send(t)} end end but i could see one going either way > Thanks, sure. it's all fun stuff eh? cheers. -a -- be kind whenever possible... it is always possible. - h.h. the 14th dali lama