Forum: Ruby pattern: auto-running module init code

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Eb9493c94d8db9887e5f15284d2c767f?d=identicon&s=25 ptkwt (Guest)
on 2005-11-23 04:14
(Received via mailing list)
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
E1d641bfe4071a5413bac781f06d3fd1?d=identicon&s=25 Sean O'halpin (sean)
on 2005-11-23 09:28
(Received via mailing list)
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 = Foo.new

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

class Foo
  include Mod
end

f = 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 = 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
~after~.

So you can have either extend before super = Foo, Mod, Bar (same as
include + super) or extend after super = 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
Eb9493c94d8db9887e5f15284d2c767f?d=identicon&s=25 ptkwt (Guest)
on 2005-11-23 10:50
(Received via mailing list)
In article <3736dd30511230027i2d31677aw4f36c01d289636f0@mail.gmail.com>,
Sean O'Halpin  <sean.ohalpin@gmail.com> wrote:
>
>  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:
>  end
>  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
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2005-11-23 14:17
(Received via mailing list)
Phil Tomson wrote:
>
> However, what if you either want to define initialize in your class (to do
>       #set up the shared tuplespace:
> following:
> their constructor?  The connection won't be setup and 'Thing' won't really be
>     end
>
>
> (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
E1d641bfe4071a5413bac781f06d3fd1?d=identicon&s=25 Sean O'halpin (sean)
on 2005-11-23 15:29
(Received via mailing list)
On 11/23/05, Phil Tomson <ptkwt@aracnet.com> wrote:
> 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 :)

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

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-level
> initialization, not instance-level.  That may or may not be what you want.

Indeed - it depends on what level you are trying to have the funky stuff
happen.
I guess it's not really relevant to what you were trying to do. Sorry
for the noise!

Regards,

Sean
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2005-11-23 19:44
(Received via mailing list)
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
Eb9493c94d8db9887e5f15284d2c767f?d=identicon&s=25 ptkwt (Guest)
on 2005-11-23 20:04
(Received via mailing list)
In article <43846a75$0$1813$edfadb0f@dread11.news.tele.dk>,
Daniel Schierbeck  <daniel.schierbeck@gmail.com> 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
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2005-11-23 20:16
(Received via mailing list)
Phil Tomson wrote:
>>     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
Eb9493c94d8db9887e5f15284d2c767f?d=identicon&s=25 ptkwt (Guest)
on 2005-11-23 21:08
(Received via mailing list)
In article <4384be41$0$1773$edfadb0f@dread11.news.tele.dk>,
Daniel Schierbeck  <daniel.schierbeck@gmail.com> wrote:
>>>       obj.do_something(*args) # whatever
>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.

>
>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
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2005-11-23 21:25
(Received via mailing list)
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
Eb9493c94d8db9887e5f15284d2c767f?d=identicon&s=25 ptkwt (Guest)
on 2005-11-23 22:33
(Received via mailing list)
In article <4384cee3$0$1760$edfadb0f@dread11.news.tele.dk>,
Daniel Schierbeck  <daniel.schierbeck@gmail.com> wrote:
>> 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.)

Actually, with your approach where you look for all the included modules
and
call each of their 'initialize' methods it shouldn't be a problem since
you
are bypassing the super mechanism.

Phil
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2005-11-23 22:50
(Received via mailing list)
Phil Tomson wrote:
>>> problem with that would be if the user of the module did happen to include
> Phil
Yeah, but calling a module's method without first including the module
in a class and then instantiating that class isn't easy. And for some
reason my Object#method seems to fail. Weird...


Cheers,
Daniel
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2005-11-25 22:30
(Received via mailing list)
A different approach than the `initialized' method, which would
naturally be run after the class's `initialize' method had been called,
would be an 'instantiated' method, which would be called *before*
`initialize'. I actually think that's better, because that makes it
possible for `initialize' to take advantage of the things changed by the
module initialization code.

   module Fooable
     def self.instantiated(obj, *args, &block)
       obj.instance_eval do
         @foo = "something"
       end
     end
   end

   class Klass
     include Fooable

     def initialize
       puts @foo  # -> something
     end
   end


This will be a lot harder to implement though, as the module method
`instantiated' must be called on all modules included by a class
*before* its `initialize' method is called. That isn't possible by
simply overriding Class#new...


Cheers,
Daniel

Cheers,
Daniel
This topic is locked and can not be replied to.