Module inclusion and class macro problem

R

Rolf Pedersen

[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
 
P

Paolo Perrotta

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
 
X

Xavier Noëlle

2010/7/25 Rolf Pedersen said:
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
 
R

Rolf Pedersen

[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 <
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
 
R

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.

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

2010/7/25 Rolf Pedersen said:
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.
 
X

Xavier Noëlle

2010/7/28 Rolf Pedersen said:
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.
 

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,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top