How to dynamically include a module and update top level?

Discussion in 'Ruby' started by Alexandre Mutel, Nov 16, 2009.

  1. 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!
    --
    Posted via http://www.ruby-forum.com/.
    Alexandre Mutel, Nov 16, 2009
    #1
    1. Advertising

  2. On 16.11.2009 22:01, Alexandre Mutel wrote:
    > 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


    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
    Robert Klemme, Nov 16, 2009
    #2
    1. Advertising

  3. Robert Klemme wrote:
    > 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...)
    --
    Posted via http://www.ruby-forum.com/.
    Alexandre Mutel, Nov 16, 2009
    #3
  4. On 17.11.2009 00:30, Alexandre Mutel wrote:
    > Robert Klemme wrote:
    >> 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?


    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
    => [#<Class:0x1021d37c>, A, B, Object, PP::ObjectMixin, Kernel, BasicObject]
    irb(main):009:0>

    > 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

    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
    Robert Klemme, Nov 17, 2009
    #4
  5. Robert Klemme wrote:
    > 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?






    --
    Posted via http://www.ruby-forum.com/.
    Alexandre Mutel, Nov 18, 2009
    #5
  6. 2009/11/18 Alexandre Mutel <>:
    > Robert Klemme wrote:
    >> Personally I find that the more interesting question. =A0Do you want to
    >> have the side effect of updating potentially many classes (and objects
    >> via their class and #extend)? =A0Maybe there is a better design choice? =

    =A0I
    >> don't know your use case or what you need that behavior for. =A0Generall=

    y
    >> Matz pics _very_ reasonable choices so I tend to assume that the
    >> aforementioned side effect is usually not wanted. =A0Which 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:


    <snip/>

    > 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/
    Robert Klemme, Nov 18, 2009
    #6
  7. Robert Klemme wrote:
    > No, and I'd rather resort to a different approach, e.g.


    Woo, thanks Robert, this is a really clean approach!
    --
    Posted via http://www.ruby-forum.com/.
    Alexandre Mutel, Nov 18, 2009
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Martin Thompson

    Changing generics in top-level module

    Martin Thompson, May 25, 2004, in forum: VHDL
    Replies:
    5
    Views:
    590
    Just an Illusion
    Jun 8, 2004
  2. Replies:
    1
    Views:
    6,479
    Tarun Batra
    Jul 11, 2005
  3. Replies:
    1
    Views:
    304
    Diez B. Roggisch
    Oct 29, 2006
  4. pabbu
    Replies:
    8
    Views:
    700
    Marc Boyer
    Nov 7, 2005
  5. Markus Fischer
    Replies:
    1
    Views:
    363
    Markus Fischer
    May 31, 2011
Loading...

Share This Page