Main Like Module is a small demonstration library that fills in the missing parts of Ruby's toplevel object so that it behaves more like a fully functional module context.
This library is not meant to be a useful tool that you should depend on in real code. It exists to make a point about a quirk in Ruby's design, and to do what it can about that quirk given the language as it stands.
When you start a Ruby program, you are not in some abstract "global" space.
You are inside an object — a real, live object that Ruby refers to internally
as main. You can prove it to yourself trivially:
puts self #=> main
puts self.class #=> ObjectSo main is just an instance of Object. That, by itself, is not strange.
What is strange is what Ruby does when you define methods or constants
at the toplevel.
When you write a method definition at the toplevel of a Ruby program, Ruby
does not attach it to main — it attaches it to Object itself, as a
private instance method. The consequence is that every object in the
entire system inherits it.
def hello
'hi'
end
"any string".send(:hello) #=> "hi"
42.send(:hello) #=> "hi"
Class.new.new.send(:hello) #=> "hi"That is a remarkable amount of action-at-a-distance for what looks like a simple, innocuous toplevel definition. The toplevel — which one might reasonably expect to be the least invasive scope in the language — is in fact the most invasive.
A more sensible design would be for main to be a self-extended module:
module Toplevel
extend self
# the user's program runs here
endUnder that design, methods defined at the toplevel would be confined to the
Toplevel module. They would still be available for the user's script (because
extend self makes them callable as instance methods on the module itself),
but they would not silently bolt themselves onto every object in the system.
Constants defined at the toplevel would be Toplevel::FOO rather than the
ambient ::FOO they are today.
Ruby already does a partial job of making main behave like a module.
Methods such as private, public, and include work at the toplevel —
they are forwarded to Object. But the proxy is incomplete: many of the
methods you would expect to find on a module context simply do not work
there. For example:
define_method(:foo) { 'bar' }
#=> NoMethodError: undefined method `define_method' for main:ObjectThis is inconsistent. If main is meant to act like the module body of
Object, then all of Module's instance methods ought to work there,
not a hand-picked subset.
The first problem — toplevel polluting Object — cannot be fixed from
userland. It is baked into the parser and the interpreter. Only a change
to Ruby itself could address it.
The second problem can be addressed, and that is what this library does.
On require, it walks Module's instance methods and, for each one not
already available at the toplevel, defines a singleton method on main
that forwards the call to Object.class_eval. The result is that anything
you can do inside a module body, you can now do at the toplevel as well.
require 'main_like_module'
define_method(:greet) { |name| "Hello, #{name}!" }
greet('World') #=> "Hello, World!"Public, private, and protected Module methods are all proxied with their
correct visibility.
Note carefully: this does nothing about Problem 1. The methods you define
at the toplevel still end up on Object. This library only completes the
proxy; it does not redesign the toplevel.
Probably not. If you want a clean module context, the right thing to do is to write one:
module MyApp
extend self
def greet(name)
"Hello, #{name}!"
end
end
MyApp.greet('World')That gives you everything main_like_module aspires to, without monkey-
patching the toplevel and without polluting Object. Treat the toplevel
as a launch pad into your own namespace and you will avoid the whole
mess.
This gem exists as a demonstration: a small, working illustration of how close Ruby comes to having a sane toplevel, and how a few dozen lines of metaprogramming are enough to close part of the gap.
$ gem install main_like_module
Or in a Gemfile:
gem 'main_like_module'Main Like Module is copyrighted open source software.
Copyright (c) 2006 Rubyworks (BSD-2-Clause)
It can be distributed and modified in accordance with the BSD-2-Clause license. See LICENSE.txt for details.