On this page:
8.2.1 The module Form
8.2.2 Submodules
8.2.3 Main and Test Submodules
0.45+9.2.0.3

8.2 Module Syntax🔗ℹ

The #lang syntax works only at the start of a Rhombus module in a file. For defining modules in other contexts, Rhombus provides a module form.

8.2.1 The module Form🔗ℹ

The a module that is not in its own file can be written as

module name_id ~lang initial_module_path:

  decl

  ....

where the name_id is a name for the module, initial_module_path is an initial import, and each decl is an import, export, definition, or expression.

The initial_module_path is needed because even the import form must be imported for further use in the module body. In other words, the initial_module_path import bootstraps the syntax that is available in the body. The most commonly used initial_module_path is rhombus (or rhombus/static), which supplies most of the bindings described in this guide, including import, def, and export.

For example, the "cake.rhm" example of the previous section could be written within another module or even in a REPL with To write this in the Rhombus REPL, it’s easiest to remove the blank lines.

module cake ~lang rhombus:

  export:

    print_cake

 

  fun print_cake(n :: Int):

    show("   ", n, Char".", "   ")

    show(" .-", n, Char"|", "-. ")

    show(" | ", n, Char" ", " | ")

    show("---", n, Char"-", "---")

 

  fun show(pre, n, ch, post):

    println(pre ++ String.make(n, ch) ++ post)

This cake module that is not associated with any file. To refer to such an unassociated module, use the self module path with !:

import self!cake open

print_cake(3)

Declaring a module does not immediately evaluate the body definitions and expressions of the module. The module must be explicitly imported at the top level to trigger evaluation. After evaluation is triggered once, later imports do not re-evaluate the module body.

module hi ~lang rhombus:

  println("Hello")

import self!hi  // prints "Hello"

import self!hi  // does not print again

Language names after #lang other than rhombus can have a different syntax for the module body, such as for #lang racket. For languages in the Rhombus family, however, a module that is documented as a “language” using the #lang notation will typically not only work with module plus ~lang, they will work with import to import the language’s bindings into a module that is implemented with a different language.

8.2.2 Submodules🔗ℹ

A module form can be nested within a module, in which case the nested module form declares a submodule. Submodules can be referenced directly by the enclosing module using self and !. The following example prints "Tony" by importing tiger from the zoo submodule:

"park.rhm"

#lang rhombus

 

module zoo ~lang rhombus:

  export:

    tiger

  def tiger = "Tony"

 

import self!zoo open

 

println(tiger)

Running a module does not necessarily run its submodules. In the above example, running "park.rhm" runs its submodule zoo only because the "park.rhm" module imports the zoo submodule. Otherwise, a module and each of its submodules can be run independently. Furthermore, if "park.rhm" is compiled to a bytecode file (via raco make), then the code for "park.rhm" or the code for zoo can be loaded independently.

Submodules can be nested within submodules, and a submodule can be referenced directly by a module other than its enclosing module by chaining ! in a module path.

A module form without ~lang is similar to a nested module form, but it inverts the possibilities for reference between the submodule and enclosing module:

  • A submodule declared with module name ~lang lang_path can be imported by its enclosing module, but the submodule cannot import the enclosing module or lexically reference the enclosing module’s bindings.

  • A submodule declared with module name (without ~lang) inherits the enclosing module’s bindings and can import the enclosing module’s bindings, but the enclosing module cannot import the submodule.

A module form without ~lang sees all of the enclosing module’s bindings—including bindings that are not exported via export.

One use of submodules declared with module without ~lang is to export additional bindings through a submodule that are not normally exported from the module:

"cake.rhm"

#lang rhombus

 

export:

  print_cake

 

fun print_cake(n :: Int):

  layer("   ", n, Char".", "   ")

  layer(" .-", n, Char"|", "-. ")

  layer(" | ", n, Char" ", " | ")

  layer("---", n, Char"-", "---")

 

fun layer(pre, n, ch, post):

  println(pre ++ String.make(n, ch) ++ post)

 

module extras:

  export:

    layer

In this revised "cake.rhm" module, layer is not imported by a module that uses import: "cake.rhm", since most clients of "cake.rhm" will not want the extra function. A module can import the extras submodule using import: "cake.rhm"!extras to access the otherwise hidden layer function.

8.2.3 Main and Test Submodules🔗ℹ

The following variant of "cake.rhm" includes a main submodule that calls print_cake:

"cake.rhm"

#lang rhombus

 

fun print_cake(n :: Int):

  layer("   ", n, Char".", "   ")

  layer(" .-", n, Char"|", "-. ")

  layer(" | ", n, Char" ", " | ")

  layer("---", n, Char"-", "---")

 

fun layer(pre, n, ch, post):

  println(pre ++ String.make(n, ch) ++ post)

 

module main:

  print_cake(10)

Running a module does not run its non-~lang submodules. Nevertheless, running the above module via rhombus, racket, or DrRacket prints a cake with 10 candles, because the main submodule is a special case.

When a module is provided as a program name to the rhombus or racket executable or run directly within DrRacket, if the module has a main submodule, the main submodule is run after its enclosing module. Declaring a main submodule thus specifies extra actions to be performed when a module is run directly, instead of imported as a library within a larger program.

A main submodule does not have to be declared without ~lang. If the main module does not need to use bindings from its enclosing module, it can be declared with ~lang and an explicit language. More commonly, main is declared without ~lang, so the submodule’s body sees the enclosing module’s bindings.

In addition, multiple module forms (without ~lang) can specify the same submodule name, in which case the bodies of the module forms are combined to create a single submodule.

The combining behavior of repeated module is particularly useful for defining a test submodule, which can be conveniently run using raco test in much the same way that main is conveniently run with rhombus or racket. For example, the following "physics.rhm" module exports drop and to_energy functions, and it defines a test module to hold unit tests:

"physics.rhm"

#lang rhombus

 

module test:

  def ϵ = 1e-10

 

export:

  drop

  to_energy

 

fun drop(t):

  1/2 * 9.8 * t * t

 

module test:

  check:

    drop(0)

    ~is_approx 0 ~within ϵ

  check:

    drop(10)

    ~is_approx 490 ~within ϵ

 

fun to_energy(m):

  m * math.expt(299792458.0, 2)

 

module test:

  check:

    to_energy(0)

    ~is_approx 0 ~within ϵ

  check:

    to_energy(1)

    ~is_approx 9e+16 ~within 1e+15

Importing "physics.rhm" into a larger program does not run the drop and to_energy tests—or even trigger the loading of the test code, if the module is compiled—but running raco test physics.rhm at a command line runs the tests.

The combining behavior of repeated module is also sometimes helpful for a main module.