[ANN] traits-0.3.0

A

Ara.T.Howard

URLS

http://raa.ruby-lang.org/search.rhtml?search=traits
http://codeforpeople.com/lib/ruby/traits

ABOUT

traits.rb aims to be a better set of attr_* methods and encourages better
living through meta-programming and uniform access priciples. traits.rb
supercedes attributes.rb. why? the name is shorter ;-)

HISTORY

0.3.0
added ability of default values to be specified with block for deferred
context sensitive initialization (see sample/c.rb)

0.1.0

completely reworked impl so NO parsing of inspect strings is required -
it's all straight methods (albeit quite confusing ones) now. the
interface is unchanged.

0.0.0

initial version


AUTHOR

ara [dot] t [dot] howard [at] noaa [dot] gov

SAMPLES

<========< sample/a.rb >========>

~ > cat sample/a.rb

require 'traits'
#
# defining a trait is like attr_accessor in the simple case
#
class C
trait :t
end

o = C::new
o.t = 42
p o.t

#
# and can be made even shorter
#

class B; has :x; end

o = B::new
o.x = 42
p o.x


~ > ruby sample/a.rb

42
42


<========< sample/b.rb >========>

~ > cat sample/b.rb

require 'traits'
#
# multiple traits can be defined at once using a list/array of string/sybmol
# arguments
#
class C
has :t0, :t1
has %w( t2 t3 )
end

obj = C::new
obj.t0 = 4
obj.t3 = 2
print obj.t0, obj.t3, "\n"

~ > ruby sample/b.rb

42


<========< sample/c.rb >========>

~ > cat sample/c.rb

require 'traits'
#
# a hash argument can be used to specify default values
#
class C
has 'a' => 4, :b => 2
end

o = C::new
print o.a, o.b, "\n"

#
# and these traits are smartly inherited
#
class K < C; end

o = K::new
o.a = 40
p( o.a + o.b ) # note that we pick up a default b from C class here since it
# has not been set

o.a = 42
o.b = nil
p( o.b || o.a ) # but not here since we've explicitly set it to nil

#
# if a block is specifed as the default the initialization of the default value
# is deferred until needed which makes for quite natural trait definitions. the
# block is passed 'self' so references to the current object can be made. (if
# this were not done 'self' in the block would be bound to the class!)
#

class C
class << self
has('classname'){|this| this.name.upcase }
end

has('classname'){|this| this.class.name.downcase }
end

class B < C; end

o = C::new
p C::classname
p o.classname

o = B::new
p B::classname
p o.classname


~ > ruby sample/c.rb

42
42
42
"C"
"c"
"B"
"b"


<========< sample/d.rb >========>

~ > cat sample/d.rb

require 'traits'
#
# all behaviours work within class scope (metal/singleton-class) to define
# class methods
#
class C
class << self
traits 'a' => 4, 'b' => 2
end
end

print C::a, C::b, "\n"

#
# singleton methods can even be defined on objects
#

class << (a = %w[dog cat ostrich])
has 'category' => 'pets'
end
p a.category

#
# and modules
#
module Mmmm
class << self; trait 'good' => 'bacon'; end
end

p Mmmm.good

~ > ruby sample/d.rb

42
"pets"
"bacon"


<========< sample/e.rb >========>

~ > cat sample/e.rb

require 'traits'
#
# shorhands exit to enter 'class << self' in order to define class traits
#
class C
class_trait 'a' => 4
c_has :b => 2
end

print C::a, C::b, "\n"

~ > ruby sample/e.rb

42


<========< sample/f.rb >========>

~ > cat sample/f.rb

require 'traits'
#
# as traits are defined they are remembered and can be accessed
#
class C
class_trait :first_class_method
trait :first_instance_method
end

class C
class_trait :second_class_method
trait :second_instance_method
end

#
# readers and writers are remembered separatedly
#
p C::class_reader_traits
p C::instance_writer_traits

#
# and can be gotten together at class or instance level
#
p C::class_traits
p C::traits

~ > ruby sample/f.rb

["first_class_method", "second_class_method"]
["first_instance_method=", "second_instance_method="]
[["first_class_method", "second_class_method"], ["first_class_method=", "second_class_method="]]
[["first_instance_method", "second_instance_method"], ["first_instance_method=", "second_instance_method="]]


<========< sample/g.rb >========>

~ > cat sample/g.rb

require 'traits'
#
# another neat feature is that they are remembered per hierarchy
#
class C
class_traits :base_class_method
trait :base_instance_method
end

class K < C
class_traits :derived_class_method
trait :derived_instance_method
end

p C::class_traits
p K::class_traits

~ > ruby sample/g.rb

[["base_class_method"], ["base_class_method="]]
[["derived_class_method", "base_class_method"], ["derived_class_method=", "base_class_method="]]


<========< sample/h.rb >========>

~ > cat sample/h.rb

require 'traits'
#
# a depth first search path is used to find defaults
#
class C
has 'a' => 42
end
class K < C; end

k = K::new
p k.a

#
# once assigned this is short-circuited
#
k.a = 'forty-two'
p k.a

~ > ruby sample/h.rb

42
"forty-two"


<========< sample/i.rb >========>

~ > cat sample/i.rb

require 'traits'
#
# getters and setters can be defined separately
#
class C
has_r :r
end
class D
has_w :w
end

#
# defining a reader trait still defines __public__ query and __private__ writer
# methods
#
class C
def using_private_writer_and_query
p r?
self.r = 42
p r
end
end
C::new.using_private_writer_and_query

#
# defining a writer trait still defines __private__ query and __private__ reader
# methods
#
class D
def using_private_reader
p w?
self.w = 'forty-two'
p w
end
end
D::new.using_private_reader

~ > ruby sample/i.rb

false
42
false
"forty-two"


<========< sample/j.rb >========>

~ > cat sample/j.rb

require 'traits'
#
# getters delegate to setters iff called with arguments
#
class AbstractWidget
class_trait 'color' => 'pinky-green'
class_trait 'size' => 42
class_trait 'shape' => 'square'

trait 'color'
trait 'size'
trait 'shape'

def initialize
color self.class.color
size self.class.size
shape self.class.shape
end
def inspect
"color <#{ color }> size <#{ size }> shape <#{ shape }>"
end
end

class BlueWidget < AbstractWidget
color 'blue'
size 420
end

p BlueWidget::new

~ > ruby sample/j.rb

color <blue> size <420> shape <square>


<========< sample/k.rb >========>

~ > cat sample/k.rb

require 'traits'
#
# the rememberance of traits can make generic intializers pretty slick
#
class C
#
# define class traits with defaults
#
class_traits(
'a' => 40,
'b' => 1,
'c' => 0
)

#
# define instance traits whose defaults come from readable class ones
#
class_rtraits.each{|ct| instance_trait ct => send(ct)}

#
# any option we respond_to? clobbers defaults
#
def initialize opts = {}
opts.each{|k,v| send(k,v) if respond_to? k}
end

#
# show anything we can read
#
def inspect
self.class.rtraits.inject(0){|n,t| n += send(t)}
end
end

c = C::new 'c' => 1
p c

~ > ruby sample/k.rb

42


<========< sample/l.rb >========>

~ > cat sample/l.rb

require 'traits'
#
# even defining single methods on object behaves
#
a = []

class << a
trait 'singleton_class' => class << self;self;end

class << self
class_trait 'x' => 42
end
end

p a.singleton_class.x

~ > ruby sample/l.rb

42


CAVEATS

this library is __experimental__


enjoy.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
R

Ralph \PJPizza\ Siegler

A

Ara.T.Howard

Hello Ara,

would it be possible to have a kind of observer on traits, kind of like Apple/obc/cocoa's KVO?

Ralph "PJPizza" Siegler

can you give me a little code snippet to show how you'd like it to look? i'm
imagining something like

class C
has 'a', 'default' => 42,
'pre' => proc{|this| puts "pre a : #{ this.a }"},
'post' => proc{|this| puts "post a : #{ this.a }"},
end

obj = C::new
obj.a('forty-two')

would result in

pre a : 42
post a : forty-two

being printed

or were you thinking more like

class C
some_observer = SomeObserver::new

has 'a', 'default' => 42,
'observer' => some_observer
end

and observer.notify(self, value) would be called or something?

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
R

Ralph \PJPizza\ Siegler

imagining something like

class C
has 'a', 'default' => 42,
'pre' => proc{|this| puts "pre a : #{ this.a }"},
'post' => proc{|this| puts "post a : #{ this.a }"},
end

obj = C::new
obj.a('forty-two')

Wow, this aspect-oriented flavor of idea is even *better* than what I was thinking of!

or were you thinking more like

class C
some_observer = SomeObserver::new

has 'a', 'default' => 42,
'observer' => some_observer
end

and observer.notify(self, value) would be called or something?

yes, that's what I was thinking, but that AO type idea is even kewler



Ralph "PJPizza" Siegler
 
J

Joel VanderWerf

Ara.T.Howard wrote:
...
<========< sample/c.rb >========> ...
class C
class << self
has('classname'){|this| this.name.upcase }
end

has('classname'){|this| this.class.name.downcase }
end

Hi, Ara,
Just curious, why wouldn't #instance_eval give you the desided behavior?
In that case, you could just use 'self' instead of 'this'.
 
A

Ara.T.Howard

Ara.T.Howard wrote:
...

Hi, Ara,
Just curious, why wouldn't #instance_eval give you the desided behavior? In
that case, you could just use 'self' instead of 'this'.

a better question is why i'm not drinking enough coffee!

look for traits 0.4.0 soon - it has this exact change ;-)

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
J

Joel VanderWerf

Ralph said:
Hello Ara,

would it be possible to have a kind of observer on traits, kind of like Apple/obc/cocoa's KVO?



Ralph "PJPizza" Siegler

I'm not sure what those observers do, but the observable module on RAA
can be used orthogonally with any pair of methods #attr and #attr=, as
in the following example:

[~] cat foo.rb
require 'observable'
class A
def foo; 3; end
def foo=(x); p x; end
extend Observable
observable :foo
end

a = A.new

a.when_foo(Object) {|val| p "changing foo to #{val.inspect}"}

a.foo = "zap"

[~] ruby foo.rb
"changing foo to 3"
"zap"
"changing foo to \"zap\""


So presumably this could be used with traits without modifying either
library.
 
A

Ara.T.Howard

Ralph said:
Hello Ara,

would it be possible to have a kind of observer on traits, kind of like
Apple/obc/cocoa's KVO?



Ralph "PJPizza" Siegler

I'm not sure what those observers do, but the observable module on RAA can be
used orthogonally with any pair of methods #attr and #attr=, as in the
following example:

[~] cat foo.rb
require 'observable'
class A
def foo; 3; end
def foo=(x); p x; end
extend Observable
observable :foo
end

a = A.new

a.when_foo(Object) {|val| p "changing foo to #{val.inspect}"}

a.foo = "zap"

[~] ruby foo.rb
"changing foo to 3"
"zap"
"changing foo to \"zap\""


So presumably this could be used with traits without modifying either
library.

good because it was giving me fits ;-) bascially it's tough to tell this

trait 'a', 'default' => 42

from

trait 'default' => 42

or this

trait 'a', 'pre' => a_proc

from this

trait 'pre' => a_proc

because i already allow this list syntax

traits 'a', 'b', 'c' => 42', 'd' => 'forty-two'

orthogonal is good! i didn't know about that btw... nice.

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
R

Ralph \PJPizza\ Siegler

I'm not sure what those observers do, but the observable module on RAA
can be used orthogonally with any pair of methods #attr and #attr=, as
in the following example:


Joel,

that's awesome, I only had known about Ruby's included Observer mixin,
not this thing.


thanks very much,


Ralph "PJPizza" Siegler
 
J

Joel VanderWerf

Ralph said:
Joel,

that's awesome, I only had known about Ruby's included Observer mixin,
not this thing.

Thanks! It's especially useful in GUI code, as in the foxtails lib,
which uses it to wire up various Fox controls and views with your apps
"model" variables.
 
D

Devin Mullins

I'm not sure I'd ever use it, but if you're looking for ideas, I think a
protocol by which to register observers would be pretty neat.

*reads email about 'observable' library*

I mean, uh, an improvement over the observable library. Something like
this, perchance:

class C
has :a, :eek:bservers => true
has :b, :validators => true
end

c = C.new
c.observe:)a) { |old,new|
blah
}
c.observe:)a) { |old,new|
additional_blah
}
c.validate:)b) { |old,new|
blah?
}

If not blah?, then b is not changed.

I'm not sure what it should do, in the observe case, should the block
throw an exception.

Also, if you could think of a good way to allow the object to internally
add observers/validators, even if observers isn't true. Ideally, modify
the protocol to make use of Ruby access control. Maybe give them
individual private methods, such as observe-a and validate-b. Maybe,
instead, just make add_observer and add_validator private methods that
do the same thing.

Or, yeah, I could just direct these suggestions to the author of
'observable'. ;)

Devin
 
J

Joel VanderWerf

Devin said:
I'm not sure I'd ever use it, but if you're looking for ideas, I think a
protocol by which to register observers would be pretty neat.

*reads email about 'observable' library*

I mean, uh, an improvement over the observable library. Something like
this, perchance:

class C
has :a, :eek:bservers => true
has :b, :validators => true
end

c = C.new
c.observe:)a) { |old,new|
blah
}
c.observe:)a) { |old,new|
additional_blah
}
c.validate:)b) { |old,new|
blah?
}

If not blah?, then b is not changed.

Currently, observers just get notified after the action has happened. A
pre-action hook might be useful. I'll look into it next time I update
'observable'.
 

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,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top