What's Wrong With Ruby Module Functions

Wednesday, October 07, 2009

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
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.

Posted by at 2009-10-07 11:55:00 +0000

blog comments powered by Disqus