Modules

File-based modules with pub visibility.

File = Module

Loon's module system is about as simple as it gets: every .loon file is a module, and the module name is just the file name without the extension. No module declarations, no package manifests, no boilerplate. If you have a file called greet.loon, you have a module called greet.

LOON
; greet.loon
[fn hello [] "hi"]
[pub fn world [] "world"]

By default, everything in a module is private. The hello function above is invisible to the outside world; only world (marked with pub) can be used by other modules. This means you get encapsulation for free. Your internal helpers stay internal.

Pub Exports

To make something visible outside its module, put pub before the definition. This works for functions, bindings, and types.

LOON
[pub fn add [a b] [+ a b]]
[pub let version "0.1.0"]
[pub type Color
  Red Green Blue]

Think of pub as the front door of your module. Everything behind that door is an implementation detail that you are free to change without worrying about breaking other code. Only the pub items form your module's contract with the outside world.

Use / Import

To bring a module into scope, use the use keyword. Once imported, you access its exports with dot syntax.

LOON
[use math]
[println [math.add 1 2]]

The module name matches the file name, so [use math] looks for math.loon. Simple and predictable. No searching through configuration files to figure out where a module comes from.

Aliasing

Sometimes module names are long or conflict with something else in scope. You can rename a module on import with as.

LOON
[use string-utils as str-u]
[println [str-u.capitalize "hello"]]

The original module name is no longer in scope after aliasing. Only the alias works. This keeps things unambiguous and lets you pick whatever name makes the most sense in context.

Selective Imports

If you only need a couple of things from a module, you can import them directly into scope. No dot prefix required.

LOON
[use math [add subtract]]
[println [add 1 2]]

This is great when you are using a function heavily and the module.function syntax starts to feel verbose. Just be careful not to import names that clash with your own definitions.

Directory Structure

For larger projects, you will want to organize modules into directories. Nested directories create module paths separated by /.

STRUCTURE
src/
  main.loon
  math.loon
  utils/
    string.loon
    io.loon
LOON
[use utils/string]
[println [string.trim "  hi  "]]

Notice that you import with the full path (utils/string) but then refer to the module by its last segment (string). This keeps usage concise while still being explicit about where things come from.

A directory with a mod.loon file exports that file as the directory module. So utils/mod.loon would let you write [use utils] directly.

Cycle Detection

Circular dependencies are a common source of confusion and bugs. Loon catches them at compile time and tells you exactly where the cycle is.

ERROR
error: circular dependency detected
  --> a.loon:1
  a.loon -> b.loon -> a.loon

If you hit this, the fix is usually straightforward: extract the shared code into a third module that both files can import. Cycles almost always mean two modules are more entangled than they should be, so breaking the cycle tends to improve your design too.