Home > database >  Why can't ruby classes inside modules have the same scope as regular classes?
Why can't ruby classes inside modules have the same scope as regular classes?

Time:10-25

I have a module with a class inside, but I find that the class inside can't reach any of the methods in the enclosing module without specifying the module path.

Another way to look at it is that the module_function doesn't seem to carry into the class.

Example:

module MyMod
  def meaning
    42
  end

  class Foo
    def initialize
      puts "New Foo"
      puts "I can call #{MyMod.meaning} from here"
      puts "But I can't get to #{meaning} from here"
    end
  end

  def go
    puts "I can get to #{meaning} from here"
    bar = Foo.new
  end

  module_function :go, :meaning
  go
end

If you run this, you get an error on the "But I can't..." line of:

undefined local variable or method `meaning'

But if you remove the MyMod bits around the whole file, it has no problem accessing the outer method.

Is there an easy way to make these accessible without having to give the full path?

(One of the reasons I am nitpicking over not including the full path is because I'm using http://redshift.sourceforge.net/script/ in order to create ruby scripts that I can instantiate - and they don't have a clear and simple name like 'MyMod' that I can just add to the path, I actually have to pass the scope around even though it's just the enclosing scope to the class)

CodePudding user response:

Because of the way Ruby name resolution and method look up works. When you write x anywhere, Ruby will, in this order:

  1. Look for a x local variable

  2. Look for a x method in self.class

  3. Look for a x method in self.superclass

  4. Repeat step 3 until superclass is nil

    Ruby will walk up the class hierarchy trying to find the method x until it reaches BasicObject.

  5. Invoke method_missing

    Its default behavior is to raise the error you encountered.

MyMod merely contains Foo, it is not part of Foo's class hierarchy. That is why the method cannot not be found.

CodePudding user response:

I have a module with a class inside,

No, you don't. You have a module definition with a class definition inside, but that does not make the class a nested class. Ruby does not have nested classes.

Ruby is not Beta, Scala, or Newspeak, there are no nested classes in Ruby.

Nesting a module or class definition inside another module or class definition does not create nesting relationship between the two classes / modules. It only makes the constant which references the class / module part of the outer class' / module's namespace.

In other words, there is no difference between

module Foo
  class Bar
  end
end

and

class Quux
end

module Foo
  Bar = Quux
end

Only the constant is nested, but not the object that is referenced by the constant.

but I find that the class inside can't reach any of the methods in the enclosing module without specifying the module path.

That is precisely because there is no "enclosing module". There is a lexically enclosing module definition but that does not create any form of relationship whatsoever between the Foo class and the MyMod module.

Another way to look at it is that the module_function doesn't seem to carry into the class.

I honestly don't understand what you mean by that, what it means for a method to "carry into a class", but Module#module_function is not magic. It does exactly what the documentation says it does: it takes an instance method of the module, copies it as an instance method of the singleton class of the module, and makes the original instance method private.

You can read its specification in the Ruby/Spec, it is fairly simple. Also, the Rubinius source code, both the basic version for booting the Rubinius kernel and the full version are fairly readable.

In the end, Module#module_function really does not do much more than

class Module
  def module_function(*meths)
    meths.each do |meth|
      define_singleton_method(meth, &instance_method(meth).bind(self))
      private meth
    end

    self
  end
end

If you run this, you get an error on the "But I can't..." line of:

undefined local variable or method `meaning'

The reason is simple: neither the class Foo nor any of its superclasses has any method of that name, so of course you get an exception.

But if you remove the MyMod bits around the whole file, it has no problem accessing the outer method.

There is no "outer method". Ruby does not have Beta-like nested classes. That is really the fundamental cause of your misunderstanding. You expect Ruby to behave like Beta, but it just doesn't. Ruby takes inspiration from any languages, most notably (in rough order of importance) Smalltalk, Lisp, Perl, and Clu, but Beta is not among them.

This here works for a completely different reason:

def meaning
  42
end

class Foo
  def initialize
    meaning
  end
end

Methods that are defined at the top-level are implicitly defined as private instance methods of Object. This is because the default definee at the top-level is ::Object. Since Foo inherits from Object, method lookup will eventually find the meaning method defined in Object.

Is there an easy way to make these accessible without having to give the full path?

Inheritance. For example, Module#append_features, which is called by Module#include, makes the module the superclass of the including class, and thus all instance methods of the module become part of the method lookup ancestry chain.

An aside: if there is no nesting, then what does Module::nesting do? Well, yeah, that is an unfortunately named method. The term "nested class" or "nested module" has a well-defined meaning in OO going all the way back to Beta. But this method is about a completely different kind of nesting:

It refers to the lexical nesting of module definitions, and not to nesting of modules themselves.

For example, these module definitions all define the exact same module, but the definition text has different nesting:

module Foo
  module Bar
    module Baz
      module Qux
        p Module.nesting
        #=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo::Bar, Foo]
      end
    end
  end
end

module Foo
  module Bar
    module Baz::Qux
      p Module.nesting
      #=> [Foo::Bar::Baz::Qux, Foo::Bar, Foo]
    end
  end
end

module Foo
  module Bar::Baz
    module Qux
      p Module.nesting
      #=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo]
    end
  end
end

module Foo::Bar
  module Baz
    module Qux
      p Module.nesting
      #=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz, Foo::Bar]
    end
  end
end

module Foo
  module Bar::Baz::Qux
    p Module.nesting
    #=> [Foo::Bar::Baz::Qux, Foo]
  end
end

module Foo::Bar::Baz
  module Qux
    p Module.nesting
    #=> [Foo::Bar::Baz::Qux, Foo::Bar::Baz]
  end
end

module Foo::Bar
  module Baz::Qux
    p Module.nesting
    #=> [Foo::Bar::Baz::Qux, Foo::Bar]
  end
end

module Foo::Bar::Baz::Qux
  p Module.nesting
  #=> [Foo::Bar::Baz::Qux]
end

Again, this is purely lexical nesting of the module definition. The module itself is not nested; the module itself is the same in all of these cases. This nesting only affects constant lookup.

Constants are looked up first lexically outwards in enclosing module definitions, then upwards the inheritance chain.

There is another instance where things can be nested: blocks create nested lexical scopes, whereas all other lexical scopes (script, module / class definition, and method definition) don't nest. In other words, blocks and only blocks have access to the local variables (and self) of their enclosing lexical scopes.

  • Related