Skip to content

A module is a namespace, and cannot be instantiated

Level: 101 · a Python programmer looking for import, and finding module instead

One line: module Shop is a named box for classes, constants and methods — Shop::Cart is a class, Shop::VERSION a constant, Shop.version a method — and Shop.new does not exist, because a module is not a class.

Ruby has no file-based namespaces: a file can define anything, and require just runs it. The namespace is the module. Writing class Cart inside module Shop gives the class the full name Shop::Cart, and another Item inside Warehouse is a different class from Shop::Item and from a top-level Item, which can always be reached as ::Item. A method defined with def self.version is a module method, called as Shop.version; module_function turns every following def into both a module method and a private instance method — the shape Math has, so that Math.sqrt works and so does include Math (the Math half is not machine-checked here). The two separators follow one rule: :: reaches a constant (a class, a module or a plain constant), . calls a method — and :: in front of a lowercase name also calls a method, so Math::sqrt works, while Shop.Cart looks for a method named Cart and fails. The one thing a module cannot do is new, because it is not a Class; a class, in turn, is a module with new added, as Class.superclass shows. The program builds a Shop and asks each of these questions.

Verified output of modules_as_namespaces_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

 1. module Shop; class Cart -- Shop::Cart.name / .new              Shop::Cart / #<Shop::Cart>
 2. Shop::Item, Warehouse::Item, ::Item -- the same class?         false / false (#<Shop::Item>, #<Warehouse::Item>)
 3. module_function: Util.slug / Post.new.path / Post.new.slug     "hello-world" / "/hello-world" / NoMethodError: private method 'slug' called for an instance of Post
 4. def self.version -- Shop.version / singleton_methods.sort      "Shop 1.0" / [:both_items, :version]
 5. Math::PI.round(2) / Math.sqrt(16) / Math::sqrt(16)             3.14 / 4.0 / 4.0
 6. :: for a constant, . for a method -- Shop.Cart                 NoMethodError: undefined method 'Cart' for module Shop
 7. inside Shop: [Item, ::Item]                                    [Shop::Item, Item]
 8. a module cannot be instantiated -- Shop.new                    NoMethodError: undefined method 'new' for module Shop
 9. Shop.constants.sort / Shop.const_get(:Cart)                    [:Cart, :Item, :VERSION] / Shop::Cart
10. Shop.class / Class.superclass (a class is a module + new)      Module / Module

Reading the output

Row 1 shows the naming: the class defined inside module Shop is Shop::Cart, and its name says so. Row 2 is why namespaces exist — three classes called Item, in Shop, in Warehouse and at the top level, and none is equal? to another. Row 3 is module_function: Util.slug works as a module method, Post, which includes Util, can call slug receiverless inside path, and Post.new.slug from outside is refused as a private method — module_function copies were made private on purpose.

Rows 4 and 5 are the two spellings on Math and on Shop: Math::PI and Shop::VERSION are constants, Math.sqrt and Shop.version are methods, and Math::sqrt works too because a lowercase name after :: is a call. Row 6 is the failure the rule predicts: Shop.Cart asks for a method called Cart. Row 7 is the top-level escape hatch: inside Shop, the bare Item is Shop::Item, and ::Item is the top-level one. Row 8 is the claim in the title — Shop.new is undefined method 'new' for module Shop — and row 10 explains it: Shop.class is Module, Class inherits from Module, and new is what the subclass adds. Row 9 is the reflective side, constants and const_get, which Constants are found lexically takes further.

Compared with Python

In Python the namespace is the file. The twin writes a real package into a temporary folder — shop/__init__.py, shop/cart.py, warehouse/__init__.py — puts the folder on sys.path and imports it, so row 1's Cart has the qualified name shop.cart.Cart: module, submodule, class. Row 2 is the same three Items, distinguished by __module__ (row 7 prints those: 'shop' and '__main__', the script's own module name). Row 3 is the plainest difference: a function at module level is just a function — shop.slug, or from shop import slug — and there is no module_function because nothing ever needs to be both a module function and a method. Rows 5 and 6 show one operator where Ruby has two: math.pi and math.sqrt are both dots, and shop.Cart is an AttributeError for the ordinary reason that Cart lives in shop.cart, not in shop. Row 8's shop() is a TypeError — a module object is not callable — which is the twin of Shop.new. Row 4's public names include cart, because importing a submodule binds it as an attribute of the package, and row 10 shows that a module is an object of type module that can even be built in memory with types.ModuleType, no file required.

Verified output of modules_as_namespaces_py.py — regenerated by tools/run_examples.py, never hand-typed.

 1. shop/cart.py -- Cart's qualified name / Cart()                 shop.cart.Cart / <shop.cart.Cart>
 2. shop.Item, warehouse.Item, Item -- the same class?             False / False (<shop.Item>, <warehouse.Item>)
 3. module function: shop.slug / import_module / Cart().slug       'hello-world' / 'hello-world' / AttributeError
 4. def version() in __init__ -- shop.version() / public names     'Shop 1.0' / ['Item', 'VERSION', 'cart', 'slug', 'version']
 5. round(math.pi, 2) / math.sqrt(16) -- one dot for both          3.14 / 4.0 / (no :: form)
 6. one dot for everything -- shop.Cart (never defined there)      AttributeError
 7. shop.Item.__module__ / Item.__module__                         ['shop', '__main__']
 8. a module cannot be instantiated -- shop()                      TypeError
 9. classes in vars(shop) / getattr(shop, "Item")                  ['Item'] / Item
10. type(shop).__name__ / types.ModuleType("util").helper()        module / 'helped'

The tutorial covers this in Modules ↗ and Packages ↗; require, the Ruby side of loading a file, is measured in require, require_relative and load.

Try it

  1. Write module Geometry with a module_function area(radius) and a nested class Circle whose area calls Geometry.area. Print Geometry.area(1), Geometry::Circle.new(2).area and Geometry.constants, then show what Geometry.new and Class.new(Geometry) raise.
  2. Replace module_function in Util with extend self, and show that Util.slug still works while Post.new.slug is now public. (Singleton classes explains where extend self puts the methods.)
Solution to 1

modules_as_namespaces_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Exercise 1: a Geometry namespace with a module_function and a nested class
# that uses it, and the two things a module cannot do.

module Geometry
  module_function

  def area(radius) = (3.14159 * radius * radius).round(2)

  class Circle
    def initialize(radius)
      @radius = radius
    end

    def area = Geometry.area(@radius)     # the module method, by its full name
    def inspect = "#<Geometry::Circle r=#{@radius}>"
  end
end

puts "Geometry.area(1):              #{Geometry.area(1)}"
puts "Geometry::Circle.new(2).area:  #{Geometry::Circle.new(2).area}"
puts "Geometry.constants:            #{Geometry.constants.inspect}"
begin
  Geometry.new
rescue NoMethodError => e
  puts "Geometry.new:                  #{e.class}: #{e.message}"
end
begin
  Class.new(Geometry)
rescue TypeError => e
  puts "Class.new(Geometry):           #{e.class}: #{e.message}"
end

Verified output of modules_as_namespaces_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

Geometry.area(1):              3.14
Geometry::Circle.new(2).area:  12.57
Geometry.constants:            [:Circle]
Geometry.new:                  NoMethodError: undefined method 'new' for module Geometry
Class.new(Geometry):           TypeError: superclass must be an instance of Class (given an instance of Module)

See also