9
OCT2008
Dual Interface Modules
I'm guessing we've all seen Ruby's Math
Module
. I'm sure you know that you can call methods in it as "module (or class) methods:"
Math.sqrt(4) # => 2.0
That's just one way to use the Math
Module
though. Another is to treat it as a mixin and call the same methods as instance methods:
module MyMathyThing
extend Math
def self.my_sqrt(*args)
sqrt(*args)
end
end
MyMathyThing.my_sqrt(4) # => 2.0
Ruby ships with a few Module
s that work like this, including the mighty Kernel
.
How is this dual interface accomplished? With the seldom seen module_function()
method. You use this much like you would private()
, to affect all following method definitions:
module Greeter
module_function
def hello
"Hello!"
end
end
module MyGreeter
extend Greeter
def self.my_hello
hello
end
end
Greeter.hello # => "Hello!"
MyGreeter.my_hello # => "Hello!"
As you can see, it magically gives us the dual interface for the methods beneath it. You can also affect specific methods by name, just as you could with private()
. This is equivalent to my definition above:
module Greeter
def hello
"Hello!"
end
module_function :hello
end
What this helper actually does is to make a copy of the method and move it up to the module interface level. Once the copy is made, they can be affected separately:
module Copies
def copy
"Copied!"
end
module_function :copy
alias_method :copier, :copy
public :copier
undef :copy
end
Copies.copy # => "Copied!"
c = Object.new.extend(Copies)
c.copier # => "Copied!"
c.copy
# ~> -:16: undefined method `copy' for #<Object:0x26e6c> (NoMethodError)
This process also marks the instance method version private()
, which is why I needed the call to public()
in the last example. This means that methods you mixin to another object do not add to its external interface:
module Selfish
module_function
def mine
"mine"
end
end
module Sharing
extend Selfish
def self.yours_and_mine
"yours and #{mine}"
end
end
Sharing.yours_and_mine # => "yours and mine"
Sharing.mine
# ~> -:18: private method `mine' called for Sharing:Module (NoMethodError)
As we've seen there are some advantages to this interface, but there are some drawbacks too. For example, I first tried to write the MyGreeter
example as:
module MyGreeter
include Greeter
module_function
def my_hello
hello
end
end
MyGreeter.my_hello # =>
# ~> -:15:in `my_hello': undefined local variable or method
# ~> `hello' for MyGreeter:Module (NameError)
# ~> from -:20
That didn't work because the Greeter
functionality was not copied up with my method. You can fix that by using a different trick to duplicate the functionality:
module MyGreeter
include Greeter
extend self # mixin functionality to our own Module interface
def my_hello
hello
end
end
MyGreeter.my_hello # => "Hello!"
This provides a similar dual interface, but there are important differences. First, we've changed the ancestors()
of MyGreeter
, not copied methods:
MyGreeter.ancestors # => [MyGreeter, Greeter]
There's just one method and changing it affects everywhere it is used.
We also didn't magically make the mixin interface private()
and it will bleed through:
module MyNestedGreeter
extend MyGreeter
def self.my_nested_hello
my_hello
end
end
MyNestedGreeter.my_nested_hello # => "Hello!"
MyNestedGreeter.my_hello # => "Hello!"
It's all tradeoffs of course, but knowing our options allows us to make informed choices about what is best for our needs.
Comments (2)
-
Dan October 23rd, 2011 Reply Link
After watching Rich Hickey's talk on simplicity vs. ease i've been looking for ways to back off creating objects and using more module for namespaces.
This works perfectly!
Where are all these functions documented?
-
If you meant
module_function
andextend
, see:ri Module#module_function ri Object#extend
If you meant where do module functions get documented, you can use
Kernel
as an example:ri Kernel
-