How do I organize code into modules?

← Getting Started · Ref: Q6

Every EK9 source file declares exactly one module with `defines module <dotted.name>`.

Three key concepts:

1. Same module, multiple files:

   Multiple .ek9 files can share the same module name. Everything in the same module is automatically visible across all files sharing that name - no import needed.

2. Cross-module access with `references`:

   To use a symbol from another module, add a `references` block after the module declaration:
     references
       other.module::SomeType
       other.module::someFunction
   Then use `SomeType` and `someFunction` directly in your code.

3. Fully qualified names (no import):

   You can skip `references` and use the full path inline:
     result <- other.module::someFunction(value)

Key rules:
- Module names must be lowercase with dot separators
- References must list each symbol explicitly (no wildcards)
- `::` separates the module name from the symbol name
- References must appear before the first construct block
- References must be in alphabetical order (E11026 if not)
- Module hierarchy is flat: `com.foo.bar` has no special access to `com.foo`
- Directory structure is your choice (unlike Java, module name does not dictate path)
- One module per file (each file declares exactly one module)

The deliberate absence of wildcards forces you to think about coupling. If your references list is getting long, it may be time to refactor.

The code example below shows a well-organized module with constants, functions, records, and a program all working together within a single module.

See Q5 for source file structure. See Q7 for managing dependencies. See Q13 for the difference between packages and modules.

Example

defines module qa.getting.started.organize.modules

  defines constant
    PI <- 3.142

  defines function
    areaOfCircle() as pure
      -> diameter as Float
      <- area as Float: PI * (diameter / 2.0) ^ 2

  defines record
    Circle
      diameter as Float: Float()

      Circle()
        -> d as Float
        diameter :=: d

      default operator ?

      operator $ as pure
        <- rtn as String: `Circle(diameter=${diameter})`

  defines program
    OrganizeModules()
      stdout <- Stdout()

      circle <- Circle(10.0)
      area <- areaOfCircle(circle.diameter)

      stdout.println("Module organization example")
      stdout.println($circle)
      stdout.println(`Area: ${area}`)

Common mistakes

E08180 — Record fields must be initialised inline. An uninitialised field triggers E08180. Use a constructor call like Float() or a literal value. See ek9 -h E08180 for details.

Incorrect:

diameter as Float

Correct:

diameter as Float: Float()

E01040 — Declaring a variable with the same name as a type defined in the module or imported via references creates a conflict. Rename the variable to avoid ambiguity with the type name. See ek9 -h E01040 for details.

Incorrect:

Circle <- Circle(10.0)

Correct:

circle <- Circle(10.0)
Other ways to ask this
  • How do modules work in EK9?
  • How do I import from another module?
  • What is the references keyword?
  • How do I access code from a different module?
  • How do I split code across files?

Coming from another language?

Python: `import module` or `from module import name`. Java: `package` declaration with `import` statements (directory must match package). Rust: `mod`/`use` with `crate::` paths. Go: `package` with `import`. Unlike Java, EK9 does not require directory structure to match module names. Unlike Python/Java, no wildcard imports allowed.

Keywords: cross-module, first, split, intro, references, start, migrate, import, files, organize, namespace, structure, beginner, module, package