Addendum to Uses For Modules, Part 3
Note: This article series on modules is also available as a PDF download. The PDF version has been revised and is more up-to-date than what you see here.
In the last issue, we discussed the use of extend self
in great detail, but neglected to cover a pair of alternatives that seem on the surface to be functionally equivalent. While I don’t want to spend too much time rehashing an old topic, I want to at least provide an example of each approach and comment on their quirks.
Defining methods at the module level
Occasionally folks ask whether mixing a module into itself via extend()
is equivalent to the code shown below.
module Greeter
def self.hello
"hi"
end
end
The short answer to that question is “no”, but it is easy to see where the confusion comes from, because calling Greeter.hello
does indeed work as expected. But the important distinction is that methods defined in this way are simply directly defined on the module itself and so cannot be mixed into anything at all. There is really very little difference between the above code and the example below.
obj = Object.new
def obj.hello
"hi"
end
Consider our earlier example of Ruby’s Math
or FileUtils
modules. With both of these modules, you can envision scenarios in which you would call the functions on the modules themselves. But there are also cases where using these modules as mixins would make a lot of sense. For example, Ruby itself ships with a math mode (-m) for irb which mixes in the Math
module at the top level so you can call its functions directly.
$ irb -m
>> sin(Math::PI/2)
=> 1.0
In the above example, if sin()
were implemented by defining the method
directly on the Math
module, there would be no way to mix it into anything.
While sometimes it might make sense to force a module to never be used as a
mixin, that use case is rare, and so little is gained by defining methods on
modules rather than using the extend self
technique.
Using module_function
Before people got in the habit of mixing modules into themselves, they often relied on a more specialized feature called module_function
to accomplish the same goals.
module Greeter
module_function
def hello
"hi"
end
end
This code allows the direct calling of Greeter.hello
, and does not prevent
Greeter
from being mixed into other objects. The module_function
approach
also allows you to choose certain methods to be module functions while
leaving others accessible via mixin only:
module Greeter
def hello
"hi"
end
def goodbye
"bye"
end
module_function :hello
end
With this modified definition, it is still possible to call Greeter.hello
, but attempting to call Greeter.goodbye
would raise a NoMethodError
. This sort of sounds like it offers the benefits of extending a module with itself, but with some added granularity. Unfortunately, there is something about module_function
that makes it quite weird to work with.
As it turns out, module_function
works very different under the hood than self-mixins do. This is because module_function
actually doesn’t manipulate the method lookup path, but instead, it makes a direct copy of the specified methods and attaches them to the module itself. If that sounds too weird to be true, check out the code below.
module Greeter
def hello
"hi"
end
module_function :hello
def hello
"howdy"
end
end
Greeter.hello #=> "hi"
class Foo
include Greeter
end
Foo.new.hello #=> "howdy"
Pretty weird behavior, right? You may find it interesting to know that I was not actually aware that module_function
made copies of methods until I wrote Issue #10 and was tipped off about this by one of our readers. However, I did know about one of the consequences of module_function
being implemented in this way: private methods cannot be used in conjunction with module_function
. That means that the following example cannot be literally translated to use module_function
.
module MinimalAnswer
extend self
def match?(pattern, input)
pattern.split(/,/).any? do |e|
normalize(input) =~ /\b#{normalize(e)}/i
end
end
private
def normalize(input)
input.downcase.strip.gsub(/\s+/," ").gsub(/[?.!\-,:'"]/, '')
end
end
From these examples, we see that module_function
is more flexible than defining methods directly on your modules, but not nearly as versatile as extending a module with itself. While the ability to selectively define which methods can be called directly on the module is nice in theory, I’ve yet to see a use case for it where it would lead to a much better design.
Reflections
With the alternatives to extend self
having unpleasant quirks, it’s no surprise that they’re quickly falling out of fashion in the Ruby world. But since no technical decision should be made based on dogma or a blind-faith acceptance of community conventions, these notes hopefully provide the necessary evidence to help you make good design decisions on your own.
NOTE: This article has also been published on the Ruby Best Practices blog. There may be additional commentary over there worth taking a look at.
Practicing Ruby is a Practicing Developer project.
All articles on this website are independently published, open source, and advertising-free.