Skip to content

rubyworks/main_like_module

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 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

This library addresses both problems, though only one of them fully.

Completing the Toplevel Proxy (Problem 2)

    require 'main_like_module'

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.

A Partial Workaround for Toplevel Pollution (Problem 1)

    require 'main_like_module/import'

The parser-level pollution of Object cannot be fixed from userland — that ship sailed when the Ruby grammar was written. But for code you write yourself, this library provides Kernel#import and Kernel#import_relative as scope-aware alternatives to require and require_relative. They evaluate the loaded script directly into the current scope rather than into the toplevel:

    require 'main_like_module/import'

    module MyApp
      import 'helpers'   # helpers.rb is evaluated into MyApp,
                         # not into Object
    end

    MyApp.some_helper_method   #=> works
    Object.private_instance_methods.include?(:some_helper_method)
    #=> false  -- Object stays clean

import searches $LOAD_PATH the way require does. import_relative resolves its argument relative to the calling file the way require_relative does. Both raise LoadError on miss.

This is a partial workaround. It only helps for code that uses import explicitly — toplevel code in scripts you don't control still pollutes Object. But for organizing your own libraries into clean module namespaces without ceremony, it is genuinely useful.

import was originally part of the rubyworks/finder gem, which has been folded into main_like_module because the two libraries are really about the same thing: making Ruby's toplevel/module loading semantics behave more sensibly.

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