Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > pattern: auto-running module init code

Reply
Thread Tools

pattern: auto-running module init code

 
 
Phil Tomson
Guest
Posts: n/a
 
      11-23-2005

Maybe you've run into this problem: you have a module and you want to make
sure that certain intialization code is run in that module. Of course a module can
have an initialize method like so:

module Foo
def initialize(a,b)
@a=a
@b=b
end
end


and then when you include the Foo module in your class later on that becomes
the initialize for your class:

class Bar
include Foo
end

Now Bar's new takes two arguments a and b.

However, what if you either want to define initialize in your class (to do
some class-specific things) or
what if you have a module that sets up some sort of connection,
perhaps a Rinda tuplespace, for example:

module Broadcaster
PROTOCOL = "druby"
HOST = "localhost"
PORT = 9999
def setup_connection
@uri = "#{@protocol||PROTOCOL}://#{@host||HOST}:#{@port||PORT}"
#set up the shared tuplespace:
DRb.start_service(@uri,Rinda::TupleSpace.new)
puts "#{$0}: setting up broadcaster..."
#now set up broadcaster:
@ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil,@uri) )
end

#other related methods that access the tuplespace
end

Now if you want your class to also be a Broadcaster you could do the
following:

class Thing
include Broadcaster
def initialize(*args)
#...
setup_connection
end
end

However, what if the user forgets to include the call to 'setup_connection' in
their constructor? The connection won't be setup and 'Thing' won't really be
doing any broadcasting.

How can we get setup_connection to be called automatically when 'Thing' is
extended with Broadcaster?

Add the following methods to the Broadcaster module:

module Broadcaster
def self.extended(obj)
obj.setup_connection
end

def self.included(mod)
warn "Be sure to run #{self}.setup_connection somewhere in #{mod}'s\
initialize method!"
warn "OR: instead of 'include #{self}', use 'extend #{self}' in \
#{mod}'s initialize method and it will be called automatically."
end
end #module

Then instead of 'include Broadcaster' use 'extend' like so:

class Thing
def initialize(*args)
#...
extend Broadcaster # setup_connection called automagically
end
end


What are the pros & cons of this approach? Is there really any advantage to
doing things this way?

(I can think of one con: if setup_connection took arguments this approach
would not work)

It does seem advantagous to call the setup code automatically this way, but it
comes at the cost of using extend instead of include (and include tends to be
used a lot more than extend ).

Phil
 
Reply With Quote
 
 
 
 
Sean O'Halpin
Guest
Posts: n/a
 
      11-23-2005
Hi Phil,

You can also use super:

class Bar
def initialize(*args, &block)
puts "in Bar"
super
end
end

class Foo < Bar
def initialize(*args, &block)
puts "in Foo"
super
end
end

f =3D Foo.new

module Mod
def initialize(*args, &block)
puts "in Mod"
super
end
end

class Foo
include Mod
end

f =3D Foo.new

--- OUTPUT ---
in Foo
in Bar
in Foo
in Mod
in Bar

or Module.included

class Bar
def initialize(*args, &block)
puts "in Bar"
super
end
end

class Foo < Bar
def initialize(*args, &block)
puts "in Foo"
super
end
end

module Mod
def self.included(obj)
puts "in Mod"
end
end

class Foo
include Mod
end

f =3D Foo.new

--- OUTPUT ---
in Mod
in Foo
in Bar

but note the different order of initialization.

With extend, you get to choose whether you extend ~before~ the super or ~af=
ter~.

So you can have either extend before super =3D Foo, Mod, Bar (same as
include + super) or extend after super =3D Foo, Bar, Mod.

Whether this is a pro or a con is up to you to decide.

My own preference in this case is to use include + super.

Regards,

Sean


 
Reply With Quote
 
 
 
 
Phil Tomson
Guest
Posts: n/a
 
      11-23-2005
In article <3736dd30511230027i2d31677aw4f36c01d289636f0@mail. gmail.com>,
Sean O'Halpin <(E-Mail Removed)> wrote:
>Hi Phil,
>
>You can also use super:
>
>class Bar
> def initialize(*args, &block)
> puts "in Bar"
> super
> end
>end
>
>class Foo < Bar
> def initialize(*args, &block)
> puts "in Foo"
> super
> end
>end
>
>f =3D Foo.new
>
>module Mod
> def initialize(*args, &block)
> puts "in Mod"
> super
> end
>end
>
>class Foo
> include Mod
>end
>
>f =3D Foo.new



Ah, that would be too simple And the user of the module has to remember
to call 'super' somewhere in the constructor, I was trying for something more
automatic

Hmmm...This brings up another thought: Just like C++ programmers are often
advised to go ahead and make their methods virtual in case someone comes along
and subclasses, should we be generally calling super in our constructors in case
someone comes along and mixes-in a module with an 'initialize' method defined?



>
>--- OUTPUT ---
>in Foo
>in Bar

# after the 2nd f.new:
>in Foo
>in Mod
>in Bar
>
>or Module.included
>
>class Bar
> def initialize(*args, &block)
> puts "in Bar"
> super
> end
>end
>
>class Foo < Bar
> def initialize(*args, &block)
> puts "in Foo"
> super
> end
>end
>
>module Mod
> def self.included(obj)
> puts "in Mod"
> end
>end


Perhaps, but isn't 'obj' in this case actually the including class?

to make it more clear, change to:
module Mod
def self.included(obj)
pus "in Mod: obj is: #{obj} #{obj.class}"
end
end

>
>class Foo
> include Mod
>end
>


Then the output will be:

in Mod: obj is: Foo Class

and it is called even before there is any instance of Foo created. So any
initialization code you want to run by using 'included' will be class-level
initialization, not instance-level. That may or may not be what you want.

Phil
 
Reply With Quote
 
Daniel Schierbeck
Guest
Posts: n/a
 
      11-23-2005
Phil Tomson wrote:
> Maybe you've run into this problem: you have a module and you want to make
> sure that certain intialization code is run in that module. Of course a module can
> have an initialize method like so:
>
> module Foo
> def initialize(a,b)
> @a=a
> @b=b
> end
> end
>
>
> and then when you include the Foo module in your class later on that becomes
> the initialize for your class:
>
> class Bar
> include Foo
> end
>
> Now Bar's new takes two arguments a and b.
>
> However, what if you either want to define initialize in your class (to do
> some class-specific things) or
> what if you have a module that sets up some sort of connection,
> perhaps a Rinda tuplespace, for example:
>
> module Broadcaster
> PROTOCOL = "druby"
> HOST = "localhost"
> PORT = 9999
> def setup_connection
> @uri = "#{@protocol||PROTOCOL}://#{@host||HOST}:#{@port||PORT}"
> #set up the shared tuplespace:
> DRb.start_service(@uri,Rinda::TupleSpace.new)
> puts "#{$0}: setting up broadcaster..."
> #now set up broadcaster:
> @ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil,@uri) )
> end
>
> #other related methods that access the tuplespace
> end
>
> Now if you want your class to also be a Broadcaster you could do the
> following:
>
> class Thing
> include Broadcaster
> def initialize(*args)
> #...
> setup_connection
> end
> end
>
> However, what if the user forgets to include the call to 'setup_connection' in
> their constructor? The connection won't be setup and 'Thing' won't really be
> doing any broadcasting.
>
> How can we get setup_connection to be called automatically when 'Thing' is
> extended with Broadcaster?
>
> Add the following methods to the Broadcaster module:
>
> module Broadcaster
> def self.extended(obj)
> obj.setup_connection
> end
>
> def self.included(mod)
> warn "Be sure to run #{self}.setup_connection somewhere in #{mod}'s\
> initialize method!"
> warn "OR: instead of 'include #{self}', use 'extend #{self}' in \
> #{mod}'s initialize method and it will be called automatically."
> end
> end #module
>
> Then instead of 'include Broadcaster' use 'extend' like so:
>
> class Thing
> def initialize(*args)
> #...
> extend Broadcaster # setup_connection called automagically
> end
> end
>
>
> What are the pros & cons of this approach? Is there really any advantage to
> doing things this way?
>
> (I can think of one con: if setup_connection took arguments this approach
> would not work)
>
> It does seem advantagous to call the setup code automatically this way, but it
> comes at the cost of using extend instead of include (and include tends to be
> used a lot more than extend ).
>
> Phil


Ideally, there would be a method hook that was called when a class
including the module was instantiated.

module Fooable
def self.initialized(obj, *args, &block)
puts "A class including Fooable has been instantiated!"
obj.do_something(*args) # whatever
end
end


Cheers,
Daniel
 
Reply With Quote
 
Sean O'Halpin
Guest
Posts: n/a
 
      11-23-2005
On 11/23/05, Phil Tomson <(E-Mail Removed)> wrote:
> Ah, that would be too simple And the user of the module has to remem=

ber
> to call 'super' somewhere in the constructor, I was trying for something =

more
> automatic


With your scheme, the user has to remember to call extend in the
initialize method, rather than include at the class level. I've seen
modules that require this cause quite a bit of confusion
unfortunately.

> Hmmm...This brings up another thought: Just like C++ programmers are oft=

en
> advised to go ahead and make their methods virtual in case someone comes =

along
> and subclasses, should we be generally calling super in our constructors =

in case
> someone comes along and mixes-in a module with an 'initialize' method def=

ined?

Well, I'm beginning to do this as a matter of course - it really does
make all this kind of stuff much simpler once you get into the habit.

>>module Mod
>> def self.included(obj)
>> puts "in Mod"
>> end
>>end


> Perhaps, but isn't 'obj' in this case actually the including class?


Yes, but I think of it as an object (too much playing about with singletons=
!).

[snip stuff about Module.included]

> So any
> initialization code you want to run by using 'included' will be class-lev=

el
> initialization, not instance-level. That may or may not be what you want=

 
Reply With Quote
 
Daniel Schierbeck
Guest
Posts: n/a
 
      11-23-2005
This is a working implementation:

class Class
alias_method :__new__, :new

def new(*args, &block)
obj = __new__(*args, &block)
included_modules.each do |mod|
if mod.respond_to? :initialized
mod.initialized(obj, *args, &block)
end
end
return obj
end
end


# Testing
module A
def self.initialized(obj, *args)
puts "Module A"
end
end

module B
def self.initialized(obj, *args)
puts "Module B"
end
end

module C; end

class Klass
include A, B, C

def initialize
puts "Klass"
end
end

Klass.new


Cheers,
Daniel
 
Reply With Quote
 
Phil Tomson
Guest
Posts: n/a
 
      11-23-2005
In article <43846a75$0$1813$(E-Mail Removed)>,
Daniel Schierbeck <(E-Mail Removed)> wrote:

>Ideally, there would be a method hook that was called when a class
>including the module was instantiated.
>
> module Fooable
> def self.initialized(obj, *args, &block)
> puts "A class including Fooable has been instantiated!"
> obj.do_something(*args) # whatever
> end
> end


But as Sean pointed out we essentially get the same behavior by calling
'super' somewhere in the including class's constructor.

Phil
 
Reply With Quote
 
Daniel Schierbeck
Guest
Posts: n/a
 
      11-23-2005
Phil Tomson wrote:
> In article <43846a75$0$1813$(E-Mail Removed)>,
> Daniel Schierbeck <(E-Mail Removed)> wrote:
>
>> Ideally, there would be a method hook that was called when a class
>> including the module was instantiated.
>>
>> module Fooable
>> def self.initialized(obj, *args, &block)
>> puts "A class including Fooable has been instantiated!"
>> obj.do_something(*args) # whatever
>> end
>> end

>
> But as Sean pointed out we essentially get the same behavior by calling
> 'super' somewhere in the including class's constructor.
>
> Phil


Yeah, but that requires the including class to add code to the
`initialize' method, some I don't think should be necessary for a module
to function. I think that the behaviour of a module should be kept
within that module, unless it needs some specific methods (like
Enumerable needs `each').

The only issue I have with my current model (see my other post for an
implementation) is that it might be better for the module initializer to
be an instance method, instead of a module method.

module Fooable
def self.initialized(obj, *args, &block); end
# versus
def initialized(*args, &block); end
end

And maybe it should be called `instantiated' instead of `initialized',
though that doesn't really make much of a difference for me.


Cheers,
Daniel
 
Reply With Quote
 
Phil Tomson
Guest
Posts: n/a
 
      11-23-2005
In article <4384be41$0$1773$(E-Mail Removed)>,
Daniel Schierbeck <(E-Mail Removed)> wrote:
>Phil Tomson wrote:
>> In article <43846a75$0$1813$(E-Mail Removed)>,
>> Daniel Schierbeck <(E-Mail Removed)> wrote:
>>
>>> Ideally, there would be a method hook that was called when a class
>>> including the module was instantiated.
>>>
>>> module Fooable
>>> def self.initialized(obj, *args, &block)
>>> puts "A class including Fooable has been instantiated!"
>>> obj.do_something(*args) # whatever
>>> end
>>> end

>>
>> But as Sean pointed out we essentially get the same behavior by calling
>> 'super' somewhere in the including class's constructor.
>>
>> Phil

>
>Yeah, but that requires the including class to add code to the
>`initialize' method, some I don't think should be necessary for a module
>to function. I think that the behaviour of a module should be kept
>within that module, unless it needs some specific methods (like
>Enumerable needs `each').


True, that's essentially what I was trying to get around (having to know to
explicitly call 'super' or some other initialization method defined in the
module.

>
>The only issue I have with my current model (see my other post for an
>implementation) is that it might be better for the module initializer to
>be an instance method, instead of a module method.
>
> module Fooable
> def self.initialized(obj, *args, &block); end
> # versus
> def initialized(*args, &block); end
> end
>
>And maybe it should be called `instantiated' instead of `initialized',
>though that doesn't really make much of a difference for me.



I do kind of like your solution, but it does mean that the implementor of the
module needs to know to define an 'initialized' method to make the mechanism
work. What if you just test for mod.respond_to? :initialize ? I suppose the
problem with that would be if the user of the module did happen to include
'super' in their constructor - if they did then the module initialization
would be called twice.

Phil
 
Reply With Quote
 
Daniel Schierbeck
Guest
Posts: n/a
 
      11-23-2005
Phil Tomson wrote:
> I do kind of like your solution, but it does mean that the implementor of the
> module needs to know to define an 'initialized' method to make the mechanism
> work.

That's no different from Module#included and Class.inherited. I think
the method belongs in the module.

> What if you just test for mod.respond_to? :initialize ? I suppose the
> problem with that would be if the user of the module did happen to include
> 'super' in their constructor - if they did then the module initialization
> would be called twice.

Yeah, and there would be the problem that multiple included modules
would override each other's `initialize' methods (as would the including
class.)


Cheers,
Daniel
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
init of class members : mem(0) vs. mem() vs. not-init at all news.aon.at C++ 11 01-29-2011 07:30 PM
questions about object initialization, default-init and value-init Jess C++ 4 05-04-2007 02:47 AM
Sequence Order between Page Init and User Control Init Tony Cheng ASP .Net 1 02-24-2006 01:56 PM
Compiler/Linker Error undefined reference to 'std::ios_base::Init::Init[in-charge]() clusardi2k@aol.com C++ 1 08-18-2005 07:11 PM
py2exe: dynamic module does not define init function Alessandro Crugnola *sephiroth* Python 6 09-22-2003 04:16 PM



Advertisments