Module inclusion and class macro problem

Discussion in 'Ruby' started by Rolf Pedersen, Jul 25, 2010.

  1. [Note: parts of this message were removed to make it a legal post.]

    Hi

    I was reading the book "Metaprogramming Ruby" which I found quite good for a
    guy trying to improve on his novice Ruby skills.
    Parts of the book describes the creation of Class macros, and I got
    intrigued by this, and I tried to create a small project to practice a
    little bit.

    Not that I know that this is a particular good idea in Ruby, I started to
    make a small framework to enforce the creation of certain methods.
    I call it interfaces, since that's what I know from C#.

    Anyway, I soon stumbled into a dead end... as expected ;o)

    Here is the code:

    module Interface
    module Base
    def self.included(base)
    base.extend ClassMethods
    end
    module ClassMethods
    def add_interface_methods(*methods)
    methods.each do |method|
    (@interface_methods ||= []) << method.to_s
    end
    end
    def implement_interfaces(*interfaces)
    (@interfaces = interfaces).each do |interface|
    puts "self: " + self.inspect.to_s
    end
    end
    def enforce_interfaces
    if not (not_implemented_interface_methods = @interface_methods -
    self.instance_methods).empty?
    raise RuntimeError, "The following interface methods are not
    implemented in class #{self.to_s}:\n " +
    not_implemented_interface_methods.inspect
    end
    end
    end
    end
    end

    module Interface
    module ILockable
    add_interface_methods :lock, :unlock
    end

    module IDrivable
    add_interface_methods :drive, :break
    end
    end

    class MyClass
    include Interface::Base
    implement_interfaces Interface::ILockable, Interface::IDrivable
    # methods and other stuff
    enforce_interfaces
    end

    When I run this I get:
    interface.rb:28: undefined method `add_interface_methods' for
    Interface::ILockable:Module (NoMethodError)

    I do understand why I get this error, but I can't see how to go about fixing
    it given that I want it structurally to be along the lines already laid out.
    Do not bother to much about the code being bloated or otherwise not in
    accordance with "standard Ruby thinking"... :eek:)

    And I do realize I didn't have to use the included hook script since I'm
    only adding class methods, but it's part of a learning process :eek:)

    So, where did I go wrong?
    Any input/solution/discussion on this issue is most welcome!

    Kind regards,
    Rolf
    Rolf Pedersen, Jul 25, 2010
    #1
    1. Advertising

  2. Hello, Rolf. Let's wrap up what happened and why. The issue comes from
    these lines:

    > module Interface
    >   module ILockable
    >     add_interface_methods :lock, :unlock
    >   end
    >
    >   # ...
    > end


    Coming from C#, you're probably tempted to assume that the code inside
    ILockable (and IDrivable) is executed only when a class/module is
    "loaded". Actually, that code is executed immediately as Ruby goes
    through the definition.

    So, at this point (and independently of all the other code in your
    example), Ruby tries to execute add_interface_methods() in the scope
    of Interface::ILockable. Inside that scope, the role of self is taken
    by Interface::ILockable itself - so that's the implicit receiver of
    any method call. So Ruby tries to call
    Interface::ILockable.add_interface_methods(), doesn't find that
    method, and fails.

    In the real world, I'd go for something simpler (and I'd be a real
    prick in questioning the notion of strongly typed interfaces in
    Ruby ;) ). However, because this is a great learning exercise, here's
    something along your own lines that probably works as you expect:

    ---
    module Interface
    module Base
    def self.included(base)
    base.extend ClassMethods
    end
    module ClassMethods
    def implement_interfaces(*interfaces)
    interfaces.each do |interface|
    @interface_methods = (@interface_methods || []) +
    interface.instance_methods
    puts "self: " + self.inspect.to_s
    end
    end
    def enforce_interfaces
    if not (not_implemented_interface_methods = @interface_methods
    - self.instance_methods).empty?
    raise RuntimeError, "The following interface methods are not
    implemented in class #{self.to_s}:\n "
    +not_implemented_interface_methods.inspect
    end
    end
    end
    end
    end

    module Interface
    module ILockable
    def lock; end
    def unlock; end
    end

    module IDrivable
    def drive; end
    def end; end
    end
    end

    class MyClass
    include Interface::Base
    implement_interfaces Interface::ILockable, Interface::IDrivable
    # methods and other stuff
    enforce_interfaces
    end


    ---
    Paolo "Nusco" Perrotta
    Metaprogramming Ruby (http://www.pragprog.com/titles/ppmetr)
    Paolo Perrotta, Jul 25, 2010
    #2
    1. Advertising

  3. 2010/7/25 Rolf Pedersen <>:
    > module Interface
    > =A0module ILockable
    > =A0 =A0add_interface_methods :lock, :unlock
    > =A0end
    >
    > =A0module IDrivable
    > =A0 =A0add_interface_methods :drive, :break
    > =A0end
    > end


    add_interface_methods is not defined for these modules.
    "include Base" in both should remove this error, but I'm not sure
    that's what you want in the end.

    I take the opportunity of this topic to provide my own version of
    interface implementation and to ask a question about it (if it's off
    topic, don't hesitate to tell me). My goal is to use your code to
    perform an automatic interface check and not bother with calling
    enforce_interface everytime:

    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
    module Interface
    def inherited(base)
    puts "#{base} inherited from me"
    enforce_interface(base)
    end

    def enforce_interface(base)
    instanceMethods =3D base.public_instance_methods()

    if (!@interface_methods.nil?())
    @interface_methods.each() do |method|
    throw "No method #{method}" unless instanceMethods.include?(method)
    end
    end
    end

    def add_interface_methods(*methods)
    methods.each do |method|
    (@interface_methods ||=3D []) << method.to_s
    end
    end
    end

    class Mother
    extend Interface

    add_interface_methods :pwet, :ziou
    end

    class Child < Mother
    def pwet()
    puts "Method pwet"
    end

    def initialize()
    puts "Child ctor"
    end
    end
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

    This piece of code almost works (so, doesn't work :)), but ends up
    (logically) with a "No method pwet" exception. I suppose that it's due
    to the fact that Child inherits from Mother before to be able to
    define any method. Is there a way around this problem ?

    I like Paolo's proposal (which could maybe be extended to allow to
    check methods arity as well), but dislike the need to call explicitely
    enforce_interface.

    --=20
    Xavier NOELLE
    Xavier Noëlle, Jul 27, 2010
    #3
  4. [Note: parts of this message were removed to make it a legal post.]

    Hi Paolo

    Thanks for answering :eek:)
    I understand that the code is executed immediately. (After all, I did read
    your book! ;o) )

    The code you proposed actually matches exactly what I had in the previous
    iteration.

    But in this next iteration, instead of defining the methods in the
    Interface::xxx modules, and thereby repeat code (def...end, and possibly in
    other cases even the body of the methods), my goal was to use a similar
    technique to the class macros, only in the modules instead.

    So is this possible to accomplish?

    Kind regards,
    -Rolf


    On Mon, Jul 26, 2010 at 12:45 AM, Paolo Perrotta <
    > wrote:

    > Hello, Rolf. Let's wrap up what happened and why. The issue comes from
    > these lines:
    >
    > > module Interface
    > > module ILockable
    > > add_interface_methods :lock, :unlock
    > > end
    > >
    > > # ...
    > > end

    >
    > Coming from C#, you're probably tempted to assume that the code inside
    > ILockable (and IDrivable) is executed only when a class/module is
    > "loaded". Actually, that code is executed immediately as Ruby goes
    > through the definition.
    >
    > So, at this point (and independently of all the other code in your
    > example), Ruby tries to execute add_interface_methods() in the scope
    > of Interface::ILockable. Inside that scope, the role of self is taken
    > by Interface::ILockable itself - so that's the implicit receiver of
    > any method call. So Ruby tries to call
    > Interface::ILockable.add_interface_methods(), doesn't find that
    > method, and fails.
    >
    > In the real world, I'd go for something simpler (and I'd be a real
    > prick in questioning the notion of strongly typed interfaces in
    > Ruby ;) ). However, because this is a great learning exercise, here's
    > something along your own lines that probably works as you expect:
    >
    > ---
    > module Interface
    > module Base
    > def self.included(base)
    > base.extend ClassMethods
    > end
    > module ClassMethods
    > def implement_interfaces(*interfaces)
    > interfaces.each do |interface|
    > @interface_methods = (@interface_methods || []) +
    > interface.instance_methods
    > puts "self: " + self.inspect.to_s
    > end
    > end
    > def enforce_interfaces
    > if not (not_implemented_interface_methods = @interface_methods
    > - self.instance_methods).empty?
    > raise RuntimeError, "The following interface methods are not
    > implemented in class #{self.to_s}:\n "
    > +not_implemented_interface_methods.inspect
    > end
    > end
    > end
    > end
    > end
    >
    > module Interface
    > module ILockable
    > def lock; end
    > def unlock; end
    > end
    >
    > module IDrivable
    > def drive; end
    > def end; end
    > end
    > end
    >
    > class MyClass
    > include Interface::Base
    > implement_interfaces Interface::ILockable, Interface::IDrivable
    > # methods and other stuff
    > enforce_interfaces
    > end
    >
    >
    > ---
    > Paolo "Nusco" Perrotta
    > Metaprogramming Ruby (http://www.pragprog.com/titles/ppmetr)
    >
    >
    Rolf Pedersen, Jul 27, 2010
    #4
  5. Hi Xavier

    Having "include Base" in both the Interface::xxx modules does remove the
    current error message.
    But the problem is that the code in the "add_interface_methods" then runs
    with Interface::xxx modules as self, and not MyClass.
    So the methods are not collected in the @interface_methods attribute of
    MyClass but in the separate Interface::xxx modules, causing
    enforce_interface to fail, because of @interface_methods in MyClass is nil.

    With regards to your code (I have just skimmed through it), I think you are
    limiting yourself to just one interface when you are using inheritage.
    My idea was to enforce compliance with a set of interfaces, by ensuring tha=
    t
    the correct methods (and maybe later on, the correct attributes) are
    implemented.
    I understand what you were trying to do by using a hook method to
    automatically run the "enforce" part at the end, but I guess that the
    inherited hook method will be called before the methods are defined, as you
    suspected yourself.
    I was actually thinking along these lines myself when I first wrote the
    code. But I concluded that I needed a class macro to be called after I
    defined the methods in MyClass.
    Now, I could have done all the work in this one class macro (doing both the
    implement_interface and enforce_interface parts in one call), but I also
    would like to have the definitions at the top of the class for esthetic
    reasons.

    But then, as I said, I also thought about hook method to replace the call t=
    o
    enforce_interface, but I couldn't find one that fits...
    Maybe Module.closing would be a good idea to have in Ruby core??? :eek:)
    Module.closing would be called just before closing a class, and could be
    used to do some checking.

    Ok, I hope that potential new readers of this thread understand that I'm no=
    t
    advocating interface checking in Ruby, It's all just a little thought
    experiment to explore the possibilities of class macros.

    Kind regards,
    Rolf

    On Tue, Jul 27, 2010 at 11:42 AM, Xavier No=EBlle <>=
    wrote:

    > 2010/7/25 Rolf Pedersen <>:
    > > module Interface
    > > module ILockable
    > > add_interface_methods :lock, :unlock
    > > end
    > >
    > > module IDrivable
    > > add_interface_methods :drive, :break
    > > end
    > > end

    >
    > add_interface_methods is not defined for these modules.
    > "include Base" in both should remove this error, but I'm not sure
    > that's what you want in the end.
    >
    > I take the opportunity of this topic to provide my own version of
    > interface implementation and to ask a question about it (if it's off
    > topic, don't hesitate to tell me). My goal is to use your code to
    > perform an automatic interface check and not bother with calling
    > enforce_interface everytime:
    >
    > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=

    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
    > module Interface
    > def inherited(base)
    > puts "#{base} inherited from me"
    > enforce_interface(base)
    > end
    >
    > def enforce_interface(base)
    > instanceMethods =3D base.public_instance_methods()
    >
    > if (!@interface_methods.nil?())
    > @interface_methods.each() do |method|
    > throw "No method #{method}" unless
    > instanceMethods.include?(method)
    > end
    > end
    > end
    >
    > def add_interface_methods(*methods)
    > methods.each do |method|
    > (@interface_methods ||=3D []) << method.to_s
    > end
    > end
    > end
    >
    > class Mother
    > extend Interface
    >
    > add_interface_methods :pwet, :ziou
    > end
    >
    > class Child < Mother
    > def pwet()
    > puts "Method pwet"
    > end
    >
    > def initialize()
    > puts "Child ctor"
    > end
    > end
    > =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=

    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
    >
    > This piece of code almost works (so, doesn't work :)), but ends up
    > (logically) with a "No method pwet" exception. I suppose that it's due
    > to the fact that Child inherits from Mother before to be able to
    > define any method. Is there a way around this problem ?
    >
    > I like Paolo's proposal (which could maybe be extended to allow to
    > check methods arity as well), but dislike the need to call explicitely
    > enforce_interface.
    >
    > --
    > Xavier NOELLE
    >
    >
    Rolf Pedersen, Jul 27, 2010
    #5
  6. 2010/7/28 Rolf Pedersen <>:
    > Hi Xavier
    >
    > Having "include Base" in both the Interface::xxx modules does remove the
    > current error message.
    > But the problem is that the code in the "add_interface_methods" then runs
    > with Interface::xxx modules as self, and not MyClass.
    > So the methods are not collected in the @interface_methods attribute of
    > MyClass but in the separate Interface::xxx modules, causing
    > enforce_interface to fail, because of @interface_methods in MyClass is nil.


    Indeed, that's what I meant by "I'm not sure that's what you want in
    the end" :)

    > With regards to your code (I have just skimmed through it), I think you are
    > limiting yourself to just one interface when you are using inheritage.
    > My idea was to enforce compliance with a set of interfaces, by ensuring that
    > the correct methods (and maybe later on, the correct attributes) are
    > implemented.


    I was trying to get something as simple as possible and only use class
    inheritance, so yes: only one possible.

    > I understand what you were trying to do by using a hook method to
    > automatically run the "enforce" part at the end, but I guess that the
    > inherited hook method will be called before the methods are defined, as you
    > suspected yourself.
    > I was actually thinking along these lines myself when I first wrote the
    > code. But I concluded that I needed a class macro to be called after I
    > defined the methods in MyClass.
    > Now, I could have done all the work in this one class macro (doing both the
    > implement_interface and enforce_interface parts in one call), but I also
    > would like to have the definitions at the top of the class for esthetic
    > reasons.


    That's what we're all looking for :)

    > But then, as I said, I also thought about hook method to replace the call to
    > enforce_interface, but I couldn't find one that fits...


    Me neither. The only way I could think of would be to hook on allocate
    or new, but, uh...this is ugly !

    > Maybe Module.closing would be a good idea to have in Ruby core??? :eek:)
    > Module.closing would be called just before closing a class, and could be
    > used to do some checking.


    That'd be great. Anyhow, I don't know if it would really make sense,
    since Ruby classes and modules may be reopened and methods added
    dynamically.

    > Ok, I hope that potential new readers of this thread understand that I'm not
    > advocating interface checking in Ruby, It's all just a little thought
    > experiment to explore the possibilities of class macros.


    I understand that it's not the Ruby way of life, due to its dynamic
    component, but sometimes I miss this feature. In my case, I would like
    to define a class which must be extended with mandatory methods. The
    way I'm dealing with it is by providing a template .rb file with empty
    mandatory methods, but that'd be great if one could just extend my
    class and see by himself which methods are missing, without needing to
    rerun the script a zillion times.

    --
    Xavier NOELLE
    Xavier Noëlle, Jul 28, 2010
    #6
  7. On Jul 27, 9:29 pm, Rolf Pedersen <> wrote:
    > So is this possible to accomplish?


    You might add add_interface_methods to the Module class. It would then
    be available to all modules.

    ---
    Paolo "Nusco" Perrotta
    Metaprogramming Ruby (http://www.pragprog.com/titles/ppmetr)
    Paolo Perrotta, Jul 28, 2010
    #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. Dead RAM
    Replies:
    20
    Views:
    1,075
    John Harrison
    Jul 14, 2004
  2. Mark J. Reed
    Replies:
    0
    Views:
    83
    Mark J. Reed
    Feb 18, 2005
  3. Trans
    Replies:
    2
    Views:
    76
    Trans
    Aug 31, 2007
  4. Jos Backus

    Ncurses module inclusion question

    Jos Backus, Aug 29, 2008, in forum: Ruby
    Replies:
    1
    Views:
    91
    John Pritchard-williams
    Aug 29, 2008
  5. Ali Asghar
    Replies:
    1
    Views:
    113
    Lasse Reichstein Nielsen
    Aug 6, 2003
Loading...

Share This Page