How to dynamically include a module and update top level?

A

Alexandre Mutel

Hi,
I'm new to Ruby programming, and I'm having some trouble to dynamically
include a module into another module. This question may have been
already posted, I tried to find it without any success, sorry to ask it
probably again (I found Ruby really powerful, but there are some obscure
behavior that i still don't understand!)

So my problem is simple. When trying with "static" include, it's working
as i understand it:

irb(main):001:0> module A
irb(main):002:1> def test()
irb(main):003:2> "test"
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> module B
irb(main):007:1> include A
irb(main):008:1> end
=> B
irb(main):009:0> include B
=> Object
irb(main):010:0> test
=> "test"

But when I'm trying to load the module A through a module_eval on module
B, this is not working:

module A
def self.included(mod)
puts "#{self} included in #{mod}"
end

def testA()
"testA"
end
end

module B
class Includer
def self.include_dyn(name)
B.module_eval "include #{name}"
end
end
end

irb(main):017:0> include B
=> Object
irb(main):018:0> Includer.include_dyn "A"
A included in B
=> B
irb(main):019:0> testA
NameError: undefined local variable or method `testA' for main:Object
from (irb):19
from :0

__________________________

The weird thing is the included callback is saying that A is include in
B... so why the testA method is not expanded to the top level?
after this, if i try to include B again, and call testA, it's working...
but i thought that as soon as a module is mixed-in, every method added
later are available to the top-includer...

It's possible to dynamically mixin A into B, and automatically having
the includer of B (the top level in my example), being updated?


Thanks!
 
R

Robert Klemme

Hi,
I'm new to Ruby programming, and I'm having some trouble to dynamically
include a module into another module. This question may have been
already posted, I tried to find it without any success, sorry to ask it
probably again (I found Ruby really powerful, but there are some obscure
behavior that i still don't understand!)

So my problem is simple. When trying with "static" include, it's working
as i understand it:

irb(main):001:0> module A
irb(main):002:1> def test()
irb(main):003:2> "test"
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> module B
irb(main):007:1> include A
irb(main):008:1> end
=> B
irb(main):009:0> include B
=> Object
irb(main):010:0> test
=> "test"

But when I'm trying to load the module A through a module_eval on module
B, this is not working:

module A
def self.included(mod)
puts "#{self} included in #{mod}"
end

def testA()
"testA"
end
end

module B
class Includer
def self.include_dyn(name)
B.module_eval "include #{name}"
end
end
end

irb(main):017:0> include B
=> Object
irb(main):018:0> Includer.include_dyn "A"
A included in B
=> B
irb(main):019:0> testA
NameError: undefined local variable or method `testA' for main:Object
from (irb):19
from :0

__________________________

The weird thing is the included callback is saying that A is include in
B... so why the testA method is not expanded to the top level?
after this, if i try to include B again, and call testA, it's working...
but i thought that as soon as a module is mixed-in, every method added
later are available to the top-includer...

It's possible to dynamically mixin A into B, and automatically having
the includer of B (the top level in my example), being updated?

The point in time of inclusion is important: your dynamic inclusion
comes after you said "include B". If you create a new class, which
includes B you will also see A's methods.

Try this

module A
def self.included(x)
printf "%p included in %p\n", x, self
end

def foo
123
end
end

module B
end

class T
include B
end

puts "initially"
p T.ancestors, B === T.new, A === T.new

module B
include A
end

puts "inclusion of A"
p T.ancestors, B === T.new, A === T.new

class S
include B
end

puts "new class with B"
p S.ancestors, B === S.new, A === S.new

You will see

$ ruby19 incl.rb
initially
[T, B, Object, Kernel, BasicObject]
true
false
B included in A
inclusion of A
[T, B, Object, Kernel, BasicObject]
true
false
new class with B
[S, B, A, Object, Kernel, BasicObject]
true
true

$

Basically "include" behaves as if the inheritance chain at the time of
inclusion is copied.

Kind regards

robert
 
A

Alexandre Mutel

Robert said:
On 16.11.2009 22:01, Alexandre Mutel wrote:
Basically "include" behaves as if the inheritance chain at the time of
inclusion is copied.

Thanks for your response. So it seems that every time a module is
mixed-in another top-module, i have to reinclude the top-module into its
includers?

I tried the following "hack" and seems to work, I would be glad to have
some feedback... not sure it's a good ruby habit... even if it's
working!

module ModuleIncluderTracker
def includers()
@includers
end

def included(mod)
puts "#{self} included in #{mod}"
if !(@includers.include? mod)
@includers << mod
end
if (mod.respond_to? :reinclude)
mod.reinclude
end
end

def reinclude()
@includers.each { |mod| puts "try to reinclude #{self} in #{mod}";
mod.module_eval "include #{self}"; }
end
end

module A
@includers = []
extend ModuleIncluderTracker
def toto()
"toto"
end
end

module B
@includers = []
extend ModuleIncluderTracker

def self.late_include(mod)
module_eval "include #{mod}"
end
end


irb(main):037:0> include B
B included in Object
=> Object

irb(main):038:0> B.late_include "A"
A included in B
try to reinclude B in Object
B included in Object
=> B

irb(main):039:0> toto
=> "toto"

With this, it keeps all the includers up-to-date with new module
included (although i have not managed any kind of circular references...
not sure if it's possible...)
 
R

Robert Klemme

Thanks for your response. So it seems that every time a module is
mixed-in another top-module, i have to reinclude the top-module into its
includers?

Apparently:

irb(main):001:0> module A; end
=> nil
irb(main):002:0> c=Class.new { include A }
=> #<Class:0x1021d37c>
irb(main):003:0> c.ancestors
=> [#<Class:0x1021d37c>, A, Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):004:0> module B; end
=> nil
irb(main):005:0> module A; include B; end
=> A
irb(main):006:0> c.ancestors
=> [#<Class:0x1021d37c>, A, Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):007:0> c.class_eval { include A }
=> #<Class:0x1021d37c>
irb(main):008:0> c.ancestors
I tried the following "hack" and seems to work, I would be glad to have
some feedback... not sure it's a good ruby habit... even if it's
working!

Personally I find that the more interesting question. Do you want to
have the side effect of updating potentially many classes (and objects
via their class and #extend)? Maybe there is a better design choice? I
don't know your use case or what you need that behavior for. Generally
Matz pics _very_ reasonable choices so I tend to assume that the
aforementioned side effect is usually not wanted. Which does not mean
that there is no use case for this.

Kind regards

robert
 
A

Alexandre Mutel

Robert said:
Personally I find that the more interesting question. Do you want to
have the side effect of updating potentially many classes (and objects
via their class and #extend)? Maybe there is a better design choice? I
don't know your use case or what you need that behavior for. Generally
Matz pics _very_ reasonable choices so I tend to assume that the
aforementioned side effect is usually not wanted. Which does not mean
that there is no use case for this.

Thanks for you response! You are right. I have resolved this without
using this trick and using plain .extends/include as you mentioned.

My use case is simple : i would like to develop a DSL language that
provides sub-DSL language that you can switch at runtime.

For example, the top DSL is inside module ALang and i have two sub
language extension in ALang_B and ALang_C like this:

module ALang
def use(sublang)
# remove all previously defined sublanguage method
# ...
# Load new sub language here
instance_eval { require "alang_#{sublang}" }
instance_eval "extend ALang_#{sublang}"
end
# here others A Language methods....
# ...
end


module ALang_B
def myABFunction()
"myAB"
end

def self.extended(base)
# perform init (declare dynamic methods...etc.)
end
end

module ALang_C
def myACFunction()
"myAC"
end

def self.extended(base)
# perform init (declare dynamic methods...etc.)
end
end

________________
Using it like this:

require "alang"
include alang

use :B
myABFunction

use :C
# myABFunction should not be usable
myACFunction

________________


Do you any project that use sub DSL language loaded/unloaded like this?
 
R

Robert Klemme

2009/11/18 Alexandre Mutel said:
Thanks for you response! You are right. I have resolved this without
using this trick and using plain .extends/include as you mentioned.

My use case is simple : i would like to develop a DSL language that
provides sub-DSL language that you can switch at runtime.

For example, the top DSL is inside module ALang and i have two sub
language extension in ALang_B and ALang_C like this:

Do you any project that use sub DSL language loaded/unloaded like this?

No, and I'd rather resort to a different approach, e.g.


module ALang_B
def myABFunction()
puts "myAB"
end
end

module ALang_C
def myACFunction()
puts "myAC"
end
end

module ALang
@langs =3D {:B =3D> ALang_B, :C =3D> ALang_C}
def self.lang(x) @langs.fetch(x) end

def use(sublang)
# printf "use %p\n", self
@lg =3D Object.new.extend(ALang.lang(sublang))
end

def self.included(x)
# printf "included %p %p %s %p\n", self, x, x.to_s, x.class
end


def method_missing(*a,&b)
# printf "missing %p\n", self
if @lg
@lg.send(*a,&b)
else
super
end
end
end

include ALang

use :B
myABFunction

begin
myACFunction
rescue =3D> e
puts e
end

use :C
# myABFunction should not be usable
myACFunction

begin
myABFunction
rescue =3D> e
puts e
end

Or pick a completely different approach where use receives a block in
which you can use the particular language, e.g.

module ALang

def use(sublang)
# printf "use %p\n", self
@lg =3D Object.new.extend(ALang.lang(sublang))
end
end

use2 :B do
myABFunction

begin
myACFunction
rescue =3D> e
puts e
end
end

use2 :C do
# myABFunction should not be usable
myACFunction

begin
myABFunction
rescue =3D> e
puts e
end
end

Kind regards

robert
--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 

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

No members online now.

Forum statistics

Threads
473,967
Messages
2,570,148
Members
46,694
Latest member
LetaCadwal

Latest Threads

Top