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

···

--
use.inject do |as, often| as.you_can - without end

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, 7:54 am, "Robert Klemme" <shortcut...@googlemail.com> wrote:

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?

why not simply

   o = OpenStruct.new :foo => 0

??

a @ http://codeforpeople.com/

···

On Sep 5, 2008, at 5:54 AM, Robert Klemme wrote:

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

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

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

# why not simply

···

From: ara.t.howard [mailto:ara.t.howard@gmail.com]
#
# 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

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

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?

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.

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

···

2008/9/5 Trans <transfire@gmail.com>:

On Sep 5, 7:54 am, "Robert Klemme" <shortcut...@googlemail.com> wrote:

--
use.inject do |as, often| as.you_can - without end

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

···

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

#
   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

pushed to rubyforge just now (0.0.3)

cheers.

a @ http://codeforpeople.com/
--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

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

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

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

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!

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.

···

On Sep 6, 1:27 pm, "Robert Klemme" <shortcut...@googlemail.com> wrote:

2008/9/5 Trans <transf...@gmail.com>:
> On Sep 5, 7:54 am, "Robert Klemme" <shortcut...@googlemail.com> wrote:

#...
# o = oo
# ...
# pushed to rubyforge just now (0.0.3)

···

From: ara.t.howard [mailto:ara.t.howard@gmail.com]
#

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

kind regars -botp

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

···

On Sun, Sep 7, 2008 at 7:00 AM, Trans <transfire@gmail.com> wrote:

--
C'est véritablement utile puisque c'est joli.

Antoine de Saint Exupéry

Sorry forgot:

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