Rubyish inst.var initializations

  • Thread starter Victor \Zverok\ Shepelev
  • Start date
V

Victor \Zverok\ Shepelev

Hi all.

I have some huge class with aspects of functionality spreaded in several
source files.

And in some of those aspects there is the same picture:
---
class MyClass
def push_something(obj)
@something_list ||= []
@something_list << obj
end

def use_something(i)
(@something_list ||= [])
end
end
---

Then I note (through profiler) various push_XXX spend too much time in
checking is @XXX_list initialized. Then I change it:

---
class MyClass
alias :initialize_without_something :initialize

def initialize(*arg)
initialize_without_something(*arg)
@something_list = []
end

def push_something(obj)
@something_list << obj
end

def use_something(i)
@something_list
end
end
---

Now push_XXX and use_XXX work cleaner and faster, but all those initialize
aliases (now I have 5 or 6) don't seem to be very elegant.

Is there better solution?

(to be clear, all those push_XXX are NOT similar - some of them push object
to hashes, others to arrays, params count differ and so on - so, they can't
be generated at once through metaprogramming)

Thanks.

V.
 
D

dblack

Hi --

Hi all.

I have some huge class with aspects of functionality spreaded in several
source files.

And in some of those aspects there is the same picture:
---
class MyClass
def push_something(obj)
@something_list ||= []
@something_list << obj
end

def use_something(i)
(@something_list ||= [])
end
end
---

Then I note (through profiler) various push_XXX spend too much time in
checking is @XXX_list initialized. Then I change it:

---
class MyClass
alias :initialize_without_something :initialize

def initialize(*arg)
initialize_without_something(*arg)
@something_list = []
end

def push_something(obj)
@something_list << obj
end

def use_something(i)
@something_list
end
end
---

Now push_XXX and use_XXX work cleaner and faster, but all those initialize
aliases (now I have 5 or 6) don't seem to be very elegant.

Is there better solution?

(to be clear, all those push_XXX are NOT similar - some of them push object
to hashes, others to arrays, params count differ and so on - so, they can't
be generated at once through metaprogramming)


In your second example, what purpose does initialize_without_something
serve? I'm wondering why you don't just do:

def initialize
@something_list = []
end

and then the other methods.


David

--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] http://www.manning.com/black | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org
 
V

Victor \Zverok\ Shepelev

From: (e-mail address removed) [mailto:[email protected]] On Behalf Of
(e-mail address removed)
Sent: Thursday, November 23, 2006 3:31 AM
Hi all.

I have some huge class with aspects of functionality spreaded in several
source files.

And in some of those aspects there is the same picture:
---
class MyClass
def push_something(obj)
@something_list ||= []
@something_list << obj
end

def use_something(i)
(@something_list ||= [])
end
end
---

Then I note (through profiler) various push_XXX spend too much time in
checking is @XXX_list initialized. Then I change it:

---
class MyClass
alias :initialize_without_something :initialize

def initialize(*arg)
initialize_without_something(*arg)
@something_list = []
end

def push_something(obj)
@something_list << obj
end

def use_something(i)
@something_list
end
end
---

Now push_XXX and use_XXX work cleaner and faster, but all those initialize
aliases (now I have 5 or 6) don't seem to be very elegant.

Is there better solution?

(to be clear, all those push_XXX are NOT similar - some of them push object
to hashes, others to arrays, params count differ and so on - so, they can't
be generated at once through metaprogramming)


In your second example, what purpose does initialize_without_something
serve? I'm wondering why you don't just do:

def initialize
@something_list = []
end

and then the other methods.


Because it would hide initialization code defined in other files for the
same class.

class A
def initialize; p 1 end
end

class A
def initialize; p 2 end
end

class A
def initialize; p 3 end
end

A.new # => 3

V.
 
J

Joel VanderWerf

Victor said:
Hi all.

I have some huge class with aspects of functionality spreaded in several
source files.

And in some of those aspects there is the same picture:
---
class MyClass
def push_something(obj)
@something_list ||= []
@something_list << obj
end

def use_something(i)
(@something_list ||= [])
end
end
---

Then I note (through profiler) various push_XXX spend too much time in
checking is @XXX_list initialized. Then I change it:

---
class MyClass
alias :initialize_without_something :initialize

def initialize(*arg)
initialize_without_something(*arg)
@something_list = []
end

def push_something(obj)
@something_list << obj
end

def use_something(i)
@something_list
end
end
---

Now push_XXX and use_XXX work cleaner and faster, but all those initialize
aliases (now I have 5 or 6) don't seem to be very elegant.

Is there better solution?

(to be clear, all those push_XXX are NOT similar - some of them push object
to hashes, others to arrays, params count differ and so on - so, they can't
be generated at once through metaprogramming)

Thanks.

V.


I don't know how this will profile--it has the disadvantage of
generating more singletons than you might want, and there is the extra
overhead of an attr_reader rather than just @something_list.

class MyClass
def method_missing(m,*)
if m == :something_list
@something_list = []
class << self
attr_reader :something_list
end
something_list
else
super
end
end

def push_something(obj)
something_list << obj
end

def use_something(i)
something_list
end
end

mc = MyClass.new

mc.push_something 3
p mc # ==> #<MyClass:0xb7d02114 @something_list=[3]>

Also, the method_missing def would need to know about each such attr,
which seems to contradict your goal of distributing that information to
lots of files. A little metaprogramming would help with that, since with
this approach you only need to metaprogram the accessors, and not all
the push_, pop_ etc. methods. Define a class method that registers
things like "something_list", and the method_missing can lookup in the
registered methods.

If you can tolerate a register method as well as the accessor overhead,
then this gives me another idea, which doesn't generate singletons:

class MyClass
@reg_methods_defined = false
@reg = {}

class << self
attr_reader :reg
def define_reg_methods
unless @reg_methods_defined
@reg.each do |m,|
attr_reader m
end
@reg_methods_defined = true
end
end
end

def initialize
self.class.define_reg_methods

self.class.reg.each do |m, (iv, bl)|
instance_variable_set(iv, bl.call)
end
end

def self.register m, &bl
@reg[m] = ["@#{m}", bl]
end
end

class MyClass
register :something_list do
[]
end

def push_something(obj)
something_list << obj
end

def use_something(i)
something_list
end
end

mc = MyClass.new

mc.push_something 3
p mc # ==> #<MyClass:0xb7d65a7c @something_list=[3]>

Getting this to work for subclasses as well is left as an exercise to
the reader ;)
 
A

ara.t.howard

Now push_XXX and use_XXX work cleaner and faster, but all those initialize
aliases (now I have 5 or 6) don't seem to be very elegant.

Is there better solution?

(to be clear, all those push_XXX are NOT similar - some of them push object
to hashes, others to arrays, params count differ and so on - so, they can't
be generated at once through metaprogramming)

i think metaprogramming is perfect: define access to the ivars (containers) to
initialize the ivar and then __redefine__ the method in-place for direct
access thereafter :

harp:~ > cat a.rb
class Module
def init_attr a, &init
ivar = "@#{ a }"
this = self
define_method a do
begin
instance_variable_set ivar, instance_eval(&init)
ensure
this.module_eval{ define_method(a){ instance_variable_get ivar } }
end
end
end
end

class C
init_attr:)list){ Array.new }
def push(val) list.push val end
end

obj = C.new
p obj.list
obj.push 42
p obj.list


harp:~ > ruby a.rb
[]
[42]


to clean it up pull 'init_attr' into it's own module and extend only those
classes that need this functionality with the module.

regards.

-a
 
A

ara.t.howard

to clean it up pull 'init_attr' into it's own module and extend only those
classes that need this functionality with the module.

since my first method was slightly flawed, here's the above plus a fix:


harp:~ > cat a.rb
module InitAttr
def init_attr a, &init
ivar = "@#{ a }"
define_method a do
this = class << self; self; end
begin
instance_variable_set ivar, instance_eval(&init)
ensure
this.module_eval{ define_method(a){ instance_variable_get ivar } }
end
end
end
end

class C
extend InitAttr
init_attr:)list){ Array.new }
def push(val) list.push val end
end

obj = C.new
p obj.list
obj.push 42
p obj.list
p C.new.list


harp:~ > ruby a.rb
[]
[42]
[]


-a
 
A

ara.t.howard

On Thu, 23 Nov 2006, Joel VanderWerf wrote:

mc = MyClass.new

mc.push_something 3
p mc # ==> #<MyClass:0xb7d65a7c @something_list=[3]>

Getting this to work for subclasses as well is left as an exercise to the
reader ;)

to do exactly this, without any extract class instance vars, plus proper
behaviour in subclasses, and not too many extract singletons, one can simply
use attributes.rb (from the metakoans.rb rubyquiz):

harp:~ > cat a.rb
require 'set'
require 'rubygems'
require 'attributes' ### gem install attributes - it's __only__ 42 lines!

class C
attribute('array'){ Array.new }
attribute('set'){ Set.new }

def initialize
self.class.attributes.each{|a| send a} # force init
end

def array_push(obj) @array << obj end
def set_push(obj) @set << obj end
end

class B < C; end


#
# works for objects
#
c = C.new
c.array_push 42
c.set_push 42
p c.array
p c.set

#
# even in subclasses
#
b = B.new
p b.array
p b.set


harp:~ > ruby a.rb
[42]
#<Set: {42}>
[]
#<Set: {}>


if you hate installing/depending-on gems then just steal the code: it's so
short you can inline it!


regards.

-a
 
R

Robert Klemme

From: (e-mail address removed) [mailto:[email protected]] On Behalf Of
(e-mail address removed)
Sent: Thursday, November 23, 2006 3:31 AM
Hi all.

I have some huge class with aspects of functionality spreaded in several
source files.

And in some of those aspects there is the same picture:
---
class MyClass
def push_something(obj)
@something_list ||= []
@something_list << obj
end

def use_something(i)
(@something_list ||= [])
end
end
---

Then I note (through profiler) various push_XXX spend too much time in
checking is @XXX_list initialized. Then I change it:

---
class MyClass
alias :initialize_without_something :initialize

def initialize(*arg)
initialize_without_something(*arg)
@something_list = []
end

def push_something(obj)
@something_list << obj
end

def use_something(i)
@something_list
end
end
---

Now push_XXX and use_XXX work cleaner and faster, but all those initialize
aliases (now I have 5 or 6) don't seem to be very elegant.

Is there better solution?

(to be clear, all those push_XXX are NOT similar - some of them push object
to hashes, others to arrays, params count differ and so on - so, they can't
be generated at once through metaprogramming)

In your second example, what purpose does initialize_without_something
serve? I'm wondering why you don't just do:

def initialize
@something_list = []
end

and then the other methods.


Because it would hide initialization code defined in other files for the
same class.

class A
def initialize; p 1 end
end

class A
def initialize; p 2 end
end

class A
def initialize; p 3 end
end

A.new # => 3


Frankly, I believe this is not a too good idea. There should be one
main place (i.e. file) responsible for this class's definition and all
other places should only add to that class. Changing initialize's
signature would certainly be not a good idea either.

If you feel, you have to initialize additional fields, then this is a
cleaner solution - and also more modular:

class Foo
def initialize(a,b,c)
super
@a=a
@b=b
end
end

module Mixin
def initialize(*a,&b)
super
@foo = []
end
end

Then you can safely do in another place:

class Foo
include Mixin
end

and construction will still work ok plus you can use that module in
several places.

Another clean solution:

class Foo
def use_sth() @something ||= [] end
def push_sth(x) something << x end
end

Btw, you can make your push_something more efficient by using a single
statement:

def push_sth(x)
(@sth ||= []) << x
end

Kind regards

robert
 
D

Daniel Schierbeck

Hi all.

I have some huge class with aspects of functionality spreaded in several
source files.

And in some of those aspects there is the same picture:
---
class MyClass
def push_something(obj)
@something_list ||= []
@something_list << obj
end

def use_something(i)
(@something_list ||= [])
end
end
---

Then I note (through profiler) various push_XXX spend too much time in
checking is @XXX_list initialized. Then I change it:

---
class MyClass
alias :initialize_without_something :initialize

def initialize(*arg)
initialize_without_something(*arg)
@something_list = []
end

def push_something(obj)
@something_list << obj
end

def use_something(i)
@something_list
end
end
---

Now push_XXX and use_XXX work cleaner and faster, but all those initialize
aliases (now I have 5 or 6) don't seem to be very elegant.

Is there better solution?

(to be clear, all those push_XXX are NOT similar - some of them push object
to hashes, others to arrays, params count differ and so on - so, they can't
be generated at once through metaprogramming)


class MyClass
def something_list
@something_list ||= []
end

def push_something(obj)
something_list << obj
end
end

Optionally you could make #something_list private if you don't wish to
expose it to the outside.


Cheers,
Daniel
 
J

Joel VanderWerf

...

Sounds like Victor was interested in avoiding the ||= in each access.
The following still does that check, though it is nice clean code, and
preferable unless profiling shows it to be a bottleneck:
class MyClass
def something_list
@something_list ||= []
end

def push_something(obj)
something_list << obj
end
end
 
V

Victor \Zverok\ Shepelev

-----Original Message-----
From: Joel VanderWerf [mailto:[email protected]]
Sent: Thursday, November 23, 2006 8:55 PM
To: ruby-talk ML
Subject: Re: Rubyish inst.var initializations

...

Sounds like Victor was interested in avoiding the ||= in each access.
The following still does that check, though it is nice clean code, and
preferable unless profiling shows it to be a bottleneck:
class MyClass
def something_list
@something_list ||= []
end

def push_something(obj)
something_list << obj
end
end

Right. And Ara's solution is completely great (I've already said this
yesterday, but don't see those mail in list - interesting, why?).

V.
 
V

Victor \Zverok\ Shepelev

From: Robert Klemme [mailto:[email protected]]
Sent: Friday, November 24, 2006 9:51 PM
From: (e-mail address removed) [mailto:[email protected]] On Behalf Of
(e-mail address removed)
Sent: Thursday, November 23, 2006 3:31 AM
On Thu, 23 Nov 2006, Victor "Zverok" Shepelev wrote:

Hi all.

I have some huge class with aspects of functionality spreaded in several
source files.
Frankly, I believe this is not a too good idea. There should be one
main place (i.e. file) responsible for this class's definition and all
other places should only add to that class. Changing initialize's
signature would certainly be not a good idea either.

If you feel, you have to initialize additional fields, then this is a
cleaner solution - and also more modular:

class Foo
def initialize(a,b,c)
super
@a=a
@b=b
end
end

module Mixin
def initialize(*a,&b)
super
@foo = []
end
end

Then you can safely do in another place:

class Foo
include Mixin
end


Robert, thanks. It's a REALLY good point.
I've completely haven't think about mixin's initialize. Would guess it.

V.
 

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,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top