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¶
- Write
module Geometrywith amodule_functionarea(radius)and a nestedclass CirclewhoseareacallsGeometry.area. PrintGeometry.area(1),Geometry::Circle.new(2).areaandGeometry.constants, then show whatGeometry.newandClass.new(Geometry)raise. - Replace
module_functioninUtilwithextend self, and show thatUtil.slugstill works whilePost.new.slugis now public. (Singleton classes explains whereextend selfputs 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.
See also¶
- Constants are found lexically, then through ancestors — how a bare name inside
ShopfindsShop::Item, and when it cannot - Mixins:
include,extend,prepend— the other job of a module: a bundle of methods to mix in - Singleton classes — where
def self.versionactually lives require,require_relativeandload— the file side: howShopgets into the process, and why loading a file twice is harmless- Modules and visibility ↗ — Rust's
modis a namespace and a privacy boundary - One module per file ↗ — Rust, like Python, maps modules onto files; Ruby does not