Initialization via a Module

G

Gavin Kistner

I have a module that needs to set a few instance variables on the
instances it is used with, during initialization.

I wrote a library that did this a while ago, and I really thought it
was working fine when I left it. It had code like this:

module Foo
attr_reader :foo
def initialize
@foo = 'foo'
super
end
end

class Bar
include Foo
attr_reader :bar
def initialize
@bar = 'bar'
end
end

b = Bar.new
p b.foo

...but Foo#initialize is not being called. Am I wrong? Did I leave my
library in a broken state, despite calling it 1.0.3 and releasing it
and using it? Or did the above used to work? (In 1.8.1 or 2 or so?)


Anyhow, I cobbled the following together, and it works, but...is
there a better/cleaner way to do the following:

module Foo
attr_reader :foo
def self.included( klass )
klass.instance_eval{
alias_method :_pre_foo_initialize, :initialize
define_method( :initialize ){ |*args|
_setup_foo
_pre_foo_initialize( *args )
}
}
end

def self.extended( obj )
obj.instance_eval{ _setup_foo }
end

private
def _setup_foo
@foo = 'foo'
end
end

class Bar
attr_reader :bar
def initialize( bar )
@bar = bar
end
include Foo
end

class Cow
attr_reader :cow
def initialize
@cow = 'cow'
end
end

b = Bar.new( 'bar' )
p b.bar
p b.foo

c = Cow.new
c.extend( Foo )
p c.cow
p c.foo
 
R

Robert Klemme

Joe said:
Errm.. isn't that what he's doing?

module Foo
attr_reader :foo
def initialize
@foo = 'foo'
super
end
end

Not in Bar as far as I can see.
*That's* the place where it should be.
:)

Sorry for the rhyme...

robert
 
J

Joe Van Dyk

=20
Not in Bar as far as I can see.
*That's* the place where it should be.

I have zero understanding of this stuff, but I thought #super called
the initialize function on the class' parent. So you'd use it for
inheritance-type stuff only and not for modules. But I'm most likely
mistaken.
 
R

Robert Klemme

Joe said:
I have zero understanding of this stuff, but I thought #super called
the initialize function on the class' parent. So you'd use it for
inheritance-type stuff only and not for modules. But I'm most likely
mistaken.

This might help clarify:

11:40:36 [robert.klemme]: irbs
module Mod;end => nil
class Foo; end => nil
class Bar<Foo;include Mod; end => Bar
Bar.ancestors => [Bar, Mod, Foo, Object, Kernel]
class Zap; include Mod end => Zap
Zap.ancestors
=> [Zap, Mod, Object, Kernel]

That's exactly the chain along which initialize super calls occur.

We had a discussion a while ago about automatically generating super class
in constructors under certain circumstances. I don't remember the subject
thogh...

Kind regards

robert
 
D

Daniel Brockman

Hi Joe,

Joe Van Dyk said:
I have zero understanding of this stuff, but I thought #super called
the initialize function on the class' parent.

That's right.
So you'd use it for inheritance-type stuff only and not for modules.

But modules are inheritance-type stuff. You should think of modules
as uninstantiable classes, and `mixin' as a warm and fuzzy euphemism
for multiple inheritance. Then it will all start to make sense.

(There are people who feel that truly unifying modules and classes to
just one essential concept would reduce this kind of confusion.)
But I'm most likely mistaken.

I don't think you realize that when you include a module `Foo' into a
module or class `Bar', then `Foo' becomes the parent of `Bar'.

The following example should illustrate this nicely:

module Foo
def initialize
puts "Foo#initialize not calling super"
end
end

class Bar
def initialize
puts "Bar#initialize is the topmost initializer"
end
end

class Baz < Bar
def initialize
puts "Baz#initialize calling super..."
super
end
end

Baz.ancestors #=> [Baz, Bar, Object, Kernel]
Baz.superclass #=> Bar

Baz.new
# Baz#initialize calling super...
# Bar#initialize is the topmost initializer

class Baz
include Foo
end

Baz.ancestors #=> [Baz, Foo, Bar, Object, Kernel]
Baz.superclass #=> Bar

Baz.new
# Baz#initialize calling super...
# Foo#initialize not calling super

Note in particular how failing to call `super' from an `initialize'
method in a module prevents the rest of the initializers from running.

That's because modules are really just emasculated classes, and method
calls work just the same across modules and classes.
 
G

Gavin Kistner

--Apple-Mail-1--930081269
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

Call `super' from `initialize'.

Gleah, of course. My library was fine, my use of it was broken.
Thanks :)

I did discover this, problem, however:

module Foo
attr_reader :foo
def initialize( *args )
super
@foo = 'foo'
end
end

class Bar
include Foo
def initialize
super
@bar = 'bar'
end
end

b = Bar.new
p b.foo #=> "foo"

class Bar2
include Foo
def initialize( bar_value )
super
@bar = bar_value
end
end

b2 = Bar2.new( 'bar2' )

/Users/gavinkistner/Desktop/tmp.rb:5:in `initialize': wrong number of
arguments (1 for 0) (ArgumentError)
from /Users/gavinkistner/Desktop/tmp.rb:5:in `initialize'
from /Users/gavinkistner/Desktop/tmp.rb:23:in `initialize'
from /Users/gavinkistner/Desktop/tmp.rb:28:in `new'
from /Users/gavinkistner/Desktop/tmp.rb:28


Obviously I can leave the call to #super out of my module, and leave
it up to the consumer of my module to know that including mine means
that any inherited class's initialization won't be called, unless the
user explicitly looks for that ancestor and binds and so on. But that
seems gross.

Is there a good solution for writing a module's initialize so that it
can properly pass initialization to the parent class first? Something
longwinded involving arity checking?


--Apple-Mail-1--930081269--
 
G

Gavin Kistner

--Apple-Mail-2--929062628
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

I think that what you want is
super(*args)

Calling super without any parentheses is a special case where it
passing along the argument list to the current function automatically.

--Apple-Mail-2--929062628--
 
R

Robert Klemme

Gavin said:
Calling super without any parentheses is a special case where it
passing along the argument list to the current function automatically.

Answering your earlier question: I'd do

module X
def initialize(*a,&b)
...
super
end
end

The problem with this (and you see it in your example) is that the super
class initialize does not take any arguments. So you should rather have
done:

class Bar2
include Foo
def initialize( bar_value )
super()
@bar = bar_value
end
end

(Explicitely use an empty arg list on super)

The whole issue is so complicated (because of dynamism, because of all
possible combinations of initialize with and without arguments and with
and without block...) that sometimes I think the mechanism should be
changed for modules. For example by allowing only initialize(*a,&b) in
modules (or redifining it to this) to avoid breaking the inheritance
initialization chain. Another option is to completely forbit initialize
in modules (which I don't like because there are cases where you rather
want it). etc.

Kind regards

robert
 
J

Joe Van Dyk

Hi Joe,
=20

=20
That's right.
=20
=20
But modules are inheritance-type stuff. You should think of modules
as uninstantiable classes, and `mixin' as a warm and fuzzy euphemism
for multiple inheritance. Then it will all start to make sense.

Perfect explanation. I thought that the methods and constants in a
module were just inserted into a class.

Thanks,
Joe
 
G

gwtmp01

Yet another reason why modules and classes should be unified.

I rather like the Object > Module > Class hierarchy
but I'd like to unify subclassing and including. They just
don't seem (in practice) to be very different from one-another.
In particular, method resolution (implicit and via super)
doesn't seem to be any different with respect to an
included module or a declared superclass. Other than
method resolution, what language features depend on the
ancestors list?


Gary Wright
 
A

Austin Ziegler

Yet another reason why modules and classes should be unified.

I don't see it, personally. I really don't see why they should be
unified in the least.

-austin
--=20
Austin Ziegler * (e-mail address removed)
* Alternate: (e-mail address removed)
 
A

Ara.T.Howard

Other than method resolution, what language features depend on the ancestors
list?

SomeClass === an_object || an_object.is_a?(Array)

...

class Class
def singleton?
not ancestors.include?(self)
end
end

i seem to end up using it alot...

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| My religion is very simple. My religion is kindness.
| --Tenzin Gyatso
===============================================================================
 
G

gwtmp01

SomeClass === an_object || an_object.is_a?(Array)

class Class
def singleton?
not ancestors.include?(self)
end
end

I wouldn't really call these language features. Can't they
just be implemented as standard library calls if you assume
the ancestors array has been constructed? And aren't they
oblivious to whether a entry in the ancestor's array arrived
there via subclassing or via including?

You certainly need the concept of an ordered list of
ancestors but do you need to have two ways (subclassing
and including) of extending that list?

Gary Wright
 

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,773
Messages
2,569,594
Members
45,120
Latest member
ShelaWalli
Top