Extend OpenStruct's functionality with explicit default value

Hi,

I just had a use case where I wanted to have several counters and not
store them in a Hash because of the nicer syntax of OpenStruct.
Currently, the code has to do

counters = OpenStruct.new

counters.foo ||= 0
counters.foo += 1

For obvious reasons I’d like to get rid of the initialization.

The suggestion would be to do this: if the argument to
OpenStruct#initialize is not a Hash use it as default value which is
returned for undefined properties. As far as I can see this won’t
break existing code since the default is nil. With the change one
could do

counters = OpenStruct.new 0

counters.foo += 1

We could go even further and copy the Hash approach by also allowing a
block which is invoked when a property is accessed for the first time.
Block argument would be the symbol of the property and the return
value would be used to initialize the property. Then you could do

data = OpenStruct.new {[]}

data.animals << “cat” << “dog”

or even

data = OpenStruct.new do |sym|
case sym
when :animals : []
when :dictionary : {}
end
end

In other words: declarative lazy initialization.

Again, existing code would not be affected.

What do others think?

Kind regards

robert

On Sep 5, 7:54 am, “Robert K.” [email protected] wrote:

counters.foo += 1
or even
Again, existing code would not be affected.

What do others think?

I recently submitted a patch that allowed OpenStruct to take a self
yielding block, e.g you could do:

data = OpenStruct.new do |o|
o.animals = []
o.dictionary = {}
end

I don’t really see that much use for complex lazy initialization as
you suggest. Though, I can see the Hash block form being useful. Maybe
that would be a better use of the block. Actually, both could be
supported if we differentiate on the arity of the block. With two
args:

OpenStruct.new{ |o, k| o[k] = [] }

Note, my patch also add #[] and #[]=, which are more important
changes.

Robert would you like to update my patch to support the Hash block
notation and resubmit it?

T.

On Sep 5, 2008, at 5:54 AM, Robert K. wrote:

counters = OpenStruct.new

counters.foo ||= 0
counters.foo += 1

For obvious reasons I’d like to get rid of the initialization.

why not simply

o = OpenStruct.new :foo => 0

??

a @ http://codeforpeople.com/

From: ara.t.howard [mailto:[email protected]]

why not simply

o = OpenStruct.new :foo => 0

because that would mean knowing in advanced the vars and mentioning
them, wc loses the dynamism of ostruct

compare to

o = OpenStruct.new(0)

unknown/new attribs will be initialized to 0 as compared to default nil

kind regards -botp

On Sep 5, 2008, at 7:51 PM, Peña, Botp wrote:

because that would mean knowing in advanced the vars and mentioning
them, wc loses the dynamism of ostruct

compare to

o = OpenStruct.new(0)

unknown/new attribs will be initialized to 0 as compared to default
nil

in all fairness that’s probably not true. aka, if the code reads

o.foo

and not

o.send :foo

then it’s probably not actually doing dynamic accumulation as the
accumulator names are know in advance and reside in the source code

if it is doing dynamic accumulation then ‘+=’ won’t do, and the code
would have to read something like

o.send( key, o.send(key) + 1 )

which is hardly worse than

o.send( key, o.send(key)||0 + 1 )

the difficulty with the default argument approach is what to do with
this

hash = { :foo => 42 }

o = OpenObject.new hash

p o.foo

does it return

42

or

{ :foo => 42 }

in otherwords it seems rather strange that you can have any default
value except a hash itself to me.

i see the usefulness though, and just added this to ‘openobject’

cfp:~/src/ruby/openobject/openobject-0.0.3 > cat a.rb

by default you cannot retrive unset values

o = oo
begin; o.foo; rescue NameError; p NameError; end

#=> NameError

but you can set anything

o = oo
o.foo = 42
p o.foo

#=> 42

blocks extend openobjects

o = oo{ def bar() 42 end }
p o.bar

#=> 42

you can set a default value which will be returned for anything

missing value

o = oo{ default 42 }
p o.bar

#=> 42

and the default value itself can be someting block/proc-like

n = 40
o = oo{ default{ n += 2 } }
p o.foo

#=> 42

cfp:~/src/ruby/openobject/openobject-0.0.3 > ruby -r lib/openobject.rb
a.rb
NameError
42
42
42
42

pushed to rubyforge just now (0.0.3)

cheers.

a @ http://codeforpeople.com/

From: ara.t.howard [mailto:[email protected]]
#…

o = oo

pushed to rubyforge just now (0.0.3)

uver cool. cool name too, oo :))
thanks, i’ll check it out.

kind regars -botp

On Sep 6, 1:27 pm, “Robert K.” [email protected] wrote:

We could go even further and copy the Hash approach by also allowing a
data = OpenStruct.new do |sym|
What do others think?
you suggest.
OpenStruct.new{ |o, k| o[k] = [] }

Note, my patch also add #[] and #[]=, which are more important
changes.

Yes, that’s a good idea!

Glad you agree. Hell of a lot faster then send(key) and
send(“#{key}=”, val).

Mention it on ruby-core!

Robert would you like to update my patch to support the Hash block
notation and resubmit it?

I would rather not want to have two interpretations for the block
because this can easily lead to confusion and subtle bugs can creep in
when accidentally having the wrong arity.

That was my first though too, but then I considered it a bit more and
think it makes enough sense. If we are asking for just the OpenStruct
object, ie. OpenStruct.new{ |o| … } then clearly we are interested
in working with the object. If we ask for the key as well, ie.
OpenStruct.new{ |o, k| … } then it is also clear we are instead
interested in doing something with a key. It’s really not any
different in principle from other method interfaces, like using one or
two arguments with #slice.

T.

Sorry, somehow I seem to have forgotten to send this earlier.

2008/9/5 Trans [email protected]:

o.animals = []
o.dictionary = {}
end

I don’t really see that much use for complex lazy initialization as
you suggest.

I don’t view it so much as complex initialization but rather
declarative lazy initialization because it saves you the effort of
writing all those getter methods.

Though, I can see the Hash block form being useful. Maybe
that would be a better use of the block. Actually, both could be
supported if we differentiate on the arity of the block. With two
args:

OpenStruct.new{ |o, k| o[k] = [] }

Note, my patch also add #[] and #[]=, which are more important
changes.

Yes, that’s a good idea!

Robert would you like to update my patch to support the Hash block
notation and resubmit it?

I would rather not want to have two interpretations for the block
because this can easily lead to confusion and subtle bugs can creep in
when accidentally having the wrong arity. Also, the initialization
you present is already possible with a Hash provided as argument.

But I can provide a patch and then we see what others on ruby-core
think.

Cheers

robert

Sorry forgot:

and implement defaulted also as a self returning instance method to
allow for this:
OpenStruct::new( :a => 42).defaulted( :a => 42 )

On Sun, Sep 7, 2008 at 7:00 AM, Trans [email protected] wrote:
I followed this discussion rather loosely, but sometimes that gives a
different perspective.

In order to avoid the hash vs. hash semantics why not just add a
different constructor?

OpenStruct::new :a => 42
behaves as before
OpenStruct::defaulted :a => 42
sets the hash :a => 42 as default value.
Naming has never been one of my many :wink: talents, but I guess you got the
idea.

Cheers
Robert


C’est véritablement utile puisque c’est joli.

Antoine de Saint Exupéry