Wednesday, October 07, 2009

What's Wrong With Ruby Module Functions

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.

2 comments:

Basti said...

Dude, this sounds like a hell of a programming language, why don't you give Python a spin ;)

Mikio Braun said...

Basti, you old python fan boy ;)

-M