[ANN] traits-0.4.0 - the coffee release

A

Ara.T.Howard

i wasn't drinking enough coffee while coding the 0.3.0 release. this is a
remedy release.


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.4.0
- tweaked writer code so multiple values can be passed to setters
- tweaked method of running blocks to use instance_eval so explicit 'this'
arg is no longer needed (though it can still be used)

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'){ name.upcase }
end

has('classname'){ self.class.classname.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
===============================================================================
 
A

Ara.T.Howard

Very very cool.

Is there a gem for this?

not yet. i'm having gem issues ;-)

i keep codeforpeople.com in sync like like

scp -r ruby/ codeforpeople.com/lib/

and all my ruby projects are updated. with rubyforge and gems i have to do
each one individually and this tends to take me a while to get to... how are
others dealing with this?

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
===============================================================================
 
J

Jeremy Hinegardner

not yet. i'm having gem issues ;-)

i keep codeforpeople.com in sync like like

scp -r ruby/ codeforpeople.com/lib/

and all my ruby projects are updated. with rubyforge and gems i have to do
each one individually and this tends to take me a while to get to... how are
others dealing with this?

Rakefile's are your friend. Check out:

rake/contrib/sshpublisher
rake/gempackagetask

enjoy,

-jeremy
 
J

Jeremy Hinegardner

--uAKRQypu60I7Lcqm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Rakefile's are your friend. Check out:

And I really mean it this time. See the attached Rakefile as an
example one for traits. I haven't yet tried recursive Rakefiles.
Executing 'rake publish' in your top level ruby directory and watching
it build gems and sync everything up, that could be nice.

There is a rake task for publishing to rubyforge, but I haven't played
with it yet.


enjoy,

-jeremy

--
========================================================================
Jeremy Hinegardner (e-mail address removed)


--uAKRQypu60I7Lcqm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=Rakefile


require 'rake'
require 'rake/clean'
require 'rake/contrib/sshpublisher'
require 'rake/gempackagetask'

PKG_VERSION = IO::read 'VERSION'
PKG_VERSION.strip!
PKG_NAME = "traits"
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"

#-----------------------------------------------------------------------
desc "Default Task"
task :default => :package

#-----------------------------------------------------------------------
# cleanup
#-----------------------------------------------------------------------
CLOBBER.include(
"pkg/**",
"README"
)
#-----------------------------------------------------------------------
# generate README
#
# - maybe make a task lib out of the gen_readme.rb code ?
#
#-----------------------------------------------------------------------
desc "Generate README"
task :readme => [:clobber] do
File.open('README','w') do |f|
$stdout = f
tmpl = IO::read 'gen_readme.rb'
eval tmpl
$stdout = STDOUT
end
end


#-----------------------------------------------------------------------
# Publish to website
#-----------------------------------------------------------------------
desc "Publish the gem on codeforpeople.com"

PUBLISH_HOST = "codeforpeople.com"
PUBLISH_ROOT = "/lib/ruby"
task :publish => [:package] do
`ssh #{PUBLISH_HOST} 'mkdir -p #{PUBLISH_ROOT}/#{PKG_NAME}/#{PKG_FILE_NAME}'`
`scp -rq ./ #{PUBLISH_HOST}:#{PUBLISH_ROOT}/#{PKG_NAME}/#{PKG_FILE_NAME}`

# if packages should go somewhere else
Rake::SshFilePublisher.new(PUBLISH_HOST, "#{PUBLISH_ROOT}/pkg", "pkg", "#{PKG_FILE_NAME}.zip").upload
Rake::SshFilePublisher.new(PUBLISH_HOST, "#{PUBLISH_ROOT}/pkg", "pkg", "#{PKG_FILE_NAME}.tgz").upload
Rake::SshFilePublisher.new(PUBLISH_HOST, "#{PUBLISH_ROOT}/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload

end

#-----------------------------------------------------------------------
# install
#-----------------------------------------------------------------------
task :install => [:package] do
`gem install pkg/#{PKG_FILE_NAME}.gem`
end

#-----------------------------------------------------------------------
# gem specification for :package
#-----------------------------------------------------------------------
task :package => [:clobber, :readme]

spec = Gem::Specification.new do |s|
s.name = PKG_NAME
s.version = PKG_VERSION
s.platform = Gem::platform::RUBY
s.summary = "better living through metaprogramming:"
s.description = <<DESC
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 ;-)
DESC

s.has_rdoc = false

# no dependencies
# s.add_dependency('gem', '>= version')

s.files = %w(README VERSION) + Dir['lib/**/*'] + Dir['sample/**/*']
s.require_path = '.'

s.author = "Ara T. Howard"
s.email = "ara [dot] t [dot] howard [at] noaa [dot] gov"

end

Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true
end

--uAKRQypu60I7Lcqm--
 
G

George Moschovitis

Hello,

This looks quite nice. I am wondering if you have seen the Property
methods used in Og. They serve a similar purpose but allow you to
attach general metadata to the generated attributes.

I think a fusion of your traits, Og's/Nitro property and perhaps some
ideas from 'ann' (as presented in the redhanded blog) would be very
interesting...

best regards,
George.
 
A

Ara.T.Howard

--uAKRQypu60I7Lcqm
Content-Type: MULTIPART/Mixed; BOUNDARY=uAKRQypu60I7Lcqm

This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.

--uAKRQypu60I7Lcqm
Content-Type: TEXT/PLAIN; CHARSET=US-ASCII; format=flowed
Content-ID: <[email protected]>
Content-Disposition: INLINE

And I really mean it this time. See the attached Rakefile as an
example one for traits. I haven't yet tried recursive Rakefiles.
Executing 'rake publish' in your top level ruby directory and watching
it build gems and sync everything up, that could be nice.

There is a rake task for publishing to rubyforge, but I haven't played
with it yet.


enjoy,

-jeremy

thanks - i'll play with it and get back to you - this is exactly what i need!

-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
===============================================================================

--uAKRQypu60I7Lcqm
Content-Type: TEXT/PLAIN; CHARSET=us-ascii
Content-ID: <[email protected]>
Content-Description:
Content-Disposition: ATTACHMENT; FILENAME=Rakefile


require 'rake'
require 'rake/clean'
require 'rake/contrib/sshpublisher'
require 'rake/gempackagetask'

PKG_VERSION = IO::read 'VERSION'
PKG_VERSION.strip!
PKG_NAME = "traits"
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"

#-----------------------------------------------------------------------
desc "Default Task"
task :default => :package

#-----------------------------------------------------------------------
# cleanup
#-----------------------------------------------------------------------
CLOBBER.include(
"pkg/**",
"README"
)
#-----------------------------------------------------------------------
# generate README
#
# - maybe make a task lib out of the gen_readme.rb code ?
#
#-----------------------------------------------------------------------
desc "Generate README"
task :readme => [:clobber] do
File.open('README','w') do |f|
$stdout = f
tmpl = IO::read 'gen_readme.rb'
eval tmpl
$stdout = STDOUT
end
end


#-----------------------------------------------------------------------
# Publish to website
#-----------------------------------------------------------------------
desc "Publish the gem on codeforpeople.com"

PUBLISH_HOST = "codeforpeople.com"
PUBLISH_ROOT = "/lib/ruby"
task :publish => [:package] do
`ssh #{PUBLISH_HOST} 'mkdir -p #{PUBLISH_ROOT}/#{PKG_NAME}/#{PKG_FILE_NAME}'`
`scp -rq ./ #{PUBLISH_HOST}:#{PUBLISH_ROOT}/#{PKG_NAME}/#{PKG_FILE_NAME}`

# if packages should go somewhere else
Rake::SshFilePublisher.new(PUBLISH_HOST, "#{PUBLISH_ROOT}/pkg", "pkg", "#{PKG_FILE_NAME}.zip").upload
Rake::SshFilePublisher.new(PUBLISH_HOST, "#{PUBLISH_ROOT}/pkg", "pkg", "#{PKG_FILE_NAME}.tgz").upload
Rake::SshFilePublisher.new(PUBLISH_HOST, "#{PUBLISH_ROOT}/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload

end

#-----------------------------------------------------------------------
# install
#-----------------------------------------------------------------------
task :install => [:package] do
`gem install pkg/#{PKG_FILE_NAME}.gem`
end

#-----------------------------------------------------------------------
# gem specification for :package
#-----------------------------------------------------------------------
task :package => [:clobber, :readme]

spec = Gem::Specification.new do |s|
s.name = PKG_NAME
s.version = PKG_VERSION
s.platform = Gem::platform::RUBY
s.summary = "better living through metaprogramming:"
s.description = <<DESC
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 ;-)
DESC

s.has_rdoc = false

# no dependencies
# s.add_dependency('gem', '>= version')

s.files = %w(README VERSION) + Dir['lib/**/*'] + Dir['sample/**/*']
s.require_path = '.'

s.author = "Ara T. Howard"
s.email = "ara [dot] t [dot] howard [at] noaa [dot] gov"

end

Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true
end

--uAKRQypu60I7Lcqm--
--uAKRQypu60I7Lcqm--
 
A

Ara.T.Howard

Hello,

This looks quite nice. I am wondering if you have seen the Property
methods used in Og. They serve a similar purpose but allow you to
attach general metadata to the generated attributes.

do they inherit correctly?
I think a fusion of your traits, Og's/Nitro property and perhaps some
ideas from 'ann' (as presented in the redhanded blog) would be very
interesting...

i'm open to suggestion!

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
===============================================================================
 
A

Ara.T.Howard

btw,

IMHO, those folding markers in the source code are uggly :(

not if you're using vim - i don't see them at all since they're folded. the
reason they are kinda big is that this for #--{{{ vs. the default #{{{ passes
through rdoc.

i do agree that they're ugly but the usefulness in vim is simply too great to
stop me from using them...

-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
===============================================================================
 
N

Nikolai Weibull

i do agree that they're ugly but the usefulness in vim is simply too
great to stop me from using them...

Why do you dedent them to column 1? Anyway, you can use
foldmethod=syntax with the vim-ruby.rubyforge.org versions (if not the
one in 6.3 (7.0 has been updated I believe)) to do what you’re doing,
which is more or less folding the body of methods,
nikolai
 
A

Ara.T.Howard

--8323328-841546791-1119544074=:5683
Content-Type: MULTIPART/MIXED; BOUNDARY="8323328-841546791-1119544074=:5683"

This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.

--8323328-841546791-1119544074=:5683
Content-Type: TEXT/PLAIN; charset=X-UNKNOWN; format=flowed
Content-Transfer-Encoding: QUOTED-PRINTABLE

Why do you dedent them to column 1? Anyway, you can use
foldmethod=3Dsyntax with the vim-ruby.rubyforge.org versions (if not the
one in 6.3 (7.0 has been updated I believe)) to do what you=E2=80=99re do= ing,
which is more or less folding the body of methods,

i can't deal with the syntax level highlighting... it's too comprehensive.
what i generally do is start out with no folds

class C
def initialize
end
end

as it grows i'll start added folds into methods. when i move up to

module M
class C
end
class B
end
end

i start folding classes, etc. also, the marker method works perfectly for
perl too...

i figure anyone who hates 'em that bad can sed them out in 2 seconds.

cheers.

-a
--=20
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D
| 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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D

--8323328-841546791-1119544074=:5683--
--8323328-841546791-1119544074=:5683--
 
N

Nikolai Weibull

Ara.T.Howard said:
i can't deal with the syntax level highlighting... it's too comprehensive.
what i generally do is start out with no folds

class C
def initialize
end
end

as it grows i'll start added folds into methods. when i move up to

You know that there are quite a number of options to fine-tune these
kinds of things for syntax-directed folding, right? For example, look
at 'foldnestmax', which is (again) precisely what you want.
module M
class C
end
class B
end
end

i start folding classes, etc. also, the marker method works perfectly for
perl too...

OK, two things:

1. Why Perl as an example?

2. Yes, but we’re not discussing Perl.

Finally, I use foldmethod=marker by default, but I don’t use them quite
like you do. I like markers for exact folding. Computers have a hard
time getting these things right, but as far as I can tell, they can do
precisely what you want correctly,
nikolai
 
A

Ara.T.Howard

You know that there are quite a number of options to fine-tune these
kinds of things for syntax-directed folding, right? For example, look
at 'foldnestmax', which is (again) precisely what you want.


OK, two things:

1. Why Perl as an example?

2. Yes, but we’re not discussing Perl.

Finally, I use foldmethod=marker by default, but I don’t use them quite
like you do. I like markers for exact folding. Computers have a hard
time getting these things right, but as far as I can tell, they can do
precisely what you want correctly,
nikolai

i concede sir! ;-) seriously, i'll read the :help folding section and
rethink.

kind regards.

-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
===============================================================================
 
G

George Moschovitis

This looks quite nice. I am wondering if you have seen the Property
methods used in Og. They serve a similar purpose but allow you to
attach general metadata to the generated attributes.

do they inherit correctly?

Yeap, AFAIK they inherit correctly ;-) Check out the source code.
i'm open to suggestion!

I would suggest to have a look at my implementation, and we can discuss
this privately or on the Og mailing list. I am sure we can come up with
something generally useful that can be included in Facets or something.

regards,
George.

ps: well i know that the folding markers are invisible in vim, but they
are still uggly ;-)
 

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

Similar Threads


Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top