I’m currently rewriting most of my JRuby/Java based machine learning toolbox, which I’m using for my research work, and stumbled upon a little Ruby “feature”: At least the way I see it, Ruby is a bit unbalanced with its scoping rules which make modules less useful as namespaces for organizing functions.
Every now and then, you will find that a certain functionality is best exposed as a set of functions, instead of a set of objects. Even Java has accepted this and provides the import static
directive since 1.5. For example, consider a module which provides all kinds of mathematical special functions like Bessel functions or the like.
The way Ruby supports module functions is through the module_function
statement. For example,
module MoreMath
module_function
def bessel(alpha, x)
...
end
end
Then you can both call it via MoreMath.bessel(a, x)
and also after you have included it in your workspace, or your class, etc.
Now, interestingly (and probably also annoyingly), you don’t see the function from any class defined in the module:
module MoreMath
... # same as above
CONSTANT = 5.43132
class SomeClass
def do_compute
x = bessel(1, 1) # <- won't work!
y = CONSTANT + 3 # <- will work!
...
end
end
end
On the other hand, constants are visible (as shown with the CONSTANT
above). In order to make bessel
visible in SomeClass
, you have to include the module in which the class is defined in the class:
module MoreMath
...
class SomeClass
include MoreMath
... # now you can use bessel()
end
end
Okay, maybe there is something fundamental which I haven’t yet grasped about Ruby, but for me this feels just wrong. The whole point of lexical scoping is that you don’t have to be explicit about what scope is active, but you can see it from the nesting structure of the code. To me it also seems like lexical scoping has been hacked into Ruby for constants (because otherwise you couldn’t probably even see other classes defined in the same module, which would be even more painful), and someone just forgot module functions. I think part of the problem is that modules also double as mixins which doesn’t, well, mix well.
By the way, another “feature” is that module functions aren’t treated properly when you include a module in another. Including a module only works with the instance functions which means that you cannot invoke the function making the module explicit:
module EvenMoreMath
include MoreMath
end
EvenMoreMath.bessel(alpha, x) # <- doesn't work, because
# include only copies
# instance methods, not
# MoreMath.bessel
For me, the cleanest way out is to collect all modules in a sub-module with a generic short name like Fcts
, then you can at least access the functions without having to fully qualify the module (which works, again because module names are constants, and the lexcial scoping works for modules):
module MoreMath
module Fcts
module_function
def bessel(alpha, x)
...
end
end
class SomeClass
def do_compute
x = Fcts::bessel(1, 1) # <- okay now
end
end
end
include MoreMath::Fcts
bessel(1, 1) # also ok now.
In summary, although I really like Ruby, I think the module scoping rules are broken as they are and using modules as namespaces for functions isn’t really working.