From: Rolf Pedersen on
[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"... :o)

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 :o)

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

Kind regards,
Rolf

From: Paolo Perrotta on
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)
From: Xavier Noëlle on
2010/7/25 Rolf Pedersen <rolfhsp(a)gmail.com>:
> 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:

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

def enforce_interface(base)
instanceMethods = 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 ||= []) << 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
=====================================

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

From: Paolo Perrotta on
On Jul 27, 9:29 pm, Rolf Pedersen <rolf...(a)gmail.com> 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)
 | 
Pages: 1
Prev: * operator, Float
Next: soap with ruby 1.9 ?