DocsLanguage

Modules and imports.

Split a program into files and expose a deliberate public interface.

A module groups related types and functions in one file. Its exposing list decides which names other modules are allowed to use, so a module can offer a clean interface and keep the rest to itself.

Create a module.

Save this as Greeting.fango:

module Greeting exposing (hello)

hello : String -> String
hello name = "Hello, #{name}!"

Then save this as main.fango in the same directory:

import Greeting

main() = print (Greeting.hello "Ada")

Run fango run main.fango and it prints Hello, Ada!. The entry file doesn’t need a module header, but every module it imports does, and the header’s name has to match the module’s path.

Import the names you use.

A plain import Greeting keeps calls qualified, as in Greeting.hello. If you’d like selected names in your local scope, add an exposing list:

import Greeting exposing (hello)

main() = print (hello "Ada")

An alias shortens the qualifier without pulling in all of the module’s names:

import Greeting as G

main() = print (G.hello "Ada")

Both versions use the Greeting.fango file above and print Hello, Ada!. Pick whichever makes a call easiest to recognize. An exposing list suits a few names you use constantly, while qualified calls help when several modules offer operations with the same name, such as read or map.

You can expose everything with (..), which is convenient in a small experiment. As a program grows, an explicit list pays off: it shows where each name comes from and avoids collisions.

Exports and visibility.

Only the names in a module’s exposing list are public. If you export a type as Point, other modules can use the type but can’t see inside it. Exporting Point(..) also exposes its constructors or record schema. That lets a module offer operations on a type while keeping its representation private, so you can change it later without breaking anyone.

Imports can be re-exported. Instances work differently: they become visible through dependencies, not through ordinary exposing lists. Type classes explains how they’re selected.

Source roots and module identity.

A dotted module name maps to directories, so Geometry.Point lives in Geometry/Point.fango, and its header should agree with that path. For an entry program, the directory of its file is the source root. Local imports resolve from there, even if you run the compiler from somewhere else.

In a REPL session, fango repl path/to/project picks the root explicitly. FANGO_ROOT is a different thing: it points at the bundled language library, not at your application’s modules.

Prelude and implicit dependencies.

The prelude supplies everyday names such as Int, String, booleans, arithmetic, the Maybe constructors, and console functions. Importing the prelude also gives you qualified access to some modules, so List.map and Maybe.withDefault just work. To call those functions without the qualifier, you need an explicit exposing import, and modules outside the prelude need imports of their own.

A library module can opt out by starting with {-# no-prelude #-} and declaring its imports explicitly. Syntax for lists, tuples, regular expressions, quotations, and deriving can bring in the bundled dependencies they need without putting every public name into scope.

Entry modules and build manifests.

The entry module is the one that supplies main. Before building a program, Fango loads its dependencies and checks their interfaces. The compiler looks after generated projects and cache manifests, so edit the Fango sources and not the generated Go files. Setup and commands covers build output and cleaning the cache.

Current naming limitation.

This is a rough edge worth knowing about when you declare effects in an entry file. An operation in the entry file can currently clash with a parameter or pattern name in an imported bundled module, and the checker may report SHADOWING inside that module even though you haven’t touched it.

The cause is that effect operations are registered before module values are checked. If an operation with a common name such as value triggers that error, give the operation a more descriptive name. In your own code, parameters and local bindings must also avoid shadowing module function names.