Skip to content

rubyworks/main_like_module

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Main Like Module

Source Code | Report Issue

Gem Version Build Status

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.

The Argument

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      #=> Object

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

Problem 1: Toplevel Pollutes Object

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
    end

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

Problem 2: The Toplevel Proxy Is Incomplete

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:Object

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

What This Library Does

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.

Should You Use This?

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.

Installation

    $ gem install main_like_module

Or in a Gemfile:

    gem 'main_like_module'

Copyrights

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.

About

Completing Toplevel's proxy of Object class

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages