Modules
One module per file; the file path is the module name. Reuse across files goes
through module / import.
Module header
An optional header names the module and lists what it exposes. It sits at column zero, above the declarations.
module Fleet exposing (internalNetwork, endpoint)
internalNetwork : StringinternalNetwork = "10.0.0.0/8"
endpoint : String -> Int -> Stringendpoint address p = "${address}:${String.fromInt p}"exposing (..) exposes everything. An explicit list names values and types;
a type may expose its constructors with Type(..):
module Roles exposing (Role(..), glyphsFor)A file with no module line is a valid entry module that exposes
everything.
An exposing list names only what the module itself declares. Exposing a name
that arrived through an import is a compile error, so a module is never a
relay for another module’s surface — importers reach a name from the module that
declares it. An uppercase item is always a type: to expose a constructor, expose
its type with Type(..).
Imports
An import line, also at column zero, brings a module into scope.
import Fleetimport Fleet as Fimport Fleet exposing (internalNetwork)import Fleet— qualified access only (Fleet.endpoint).import Fleet as F— qualified under the alias (F.endpoint).import Fleet exposing (internalNetwork)— the named values usable unqualified.
Only exposed names are importable. A type exposed with Type(..) brings its
constructors into the importer — usable both to build values and to
pattern-match, with the exhaustiveness checker seeing the full constructor set.
A type exposed without (..) keeps its constructors private, so it cannot be
matched in the importer.
Names that two modules share
A type and a constructor are both identified by the module that declares them.
IntBox’s Thing is IntBox.Thing, and Shapes’s Circle is
Shapes.Circle, so two modules may each declare a Thing or a Circle and be
imported together.
Names are written bare, and a bare name resolves whenever exactly one thing in scope answers to it. When two do, write the one you mean in full — in an annotation, in an expression, or in a pattern:
import IntBox exposing (Thing(..)) -- declares Thing, whose variant is Wrapimport StrBox exposing (Thing(..)) -- also declares Thing, also with a Wrap
label : IntBox.Thing -> Stringlabel value = case value of IntBox.Wrap n -> String.fromInt nA bare Thing or a bare Wrap in that module is a compile error naming both
candidates and offering both spellings. It is reported where the name is
written, not at the import — the imports are fine, and only a use site can
be ambiguous. Renaming one of the two remains available and is often the
clearer answer.
A type reached through two imports is one type, not a collision: two modules may
both re-expose a third module’s type. Constructors do not travel that way — a
module cannot re-expose a constructor it did not declare, so a constructor’s
qualifier is always the module that declared it, and one module reached through
two import lines contributes one name.
Constructors are also gated tighter than types. Only a Type(..) export puts a
constructor in an importer’s scope, so a type exposed without (..) puts no
constructor name in play at all. A type name, by contrast, is in play even
when the module keeps the type itself unexposed and only mentions it in an
exposed signature, because the importer’s inference still unifies on it.
Qualified access
Alias.name (or Module.name) reaches an exposed member. This reuses the same
dotted-name resolution as the built-in modules (List.map, String.fromInt).
See the prelude.
The case of what follows the last dot is what picks the namespace:
Limesurvey.Database.config is the value config, and
Limesurvey.Database.Config is the type or constructor Config. An as alias
stands in for the module name in all three.
The main entry point
Exactly one module — the entry — declares main : List Scroll. Every other
module is a library. A library that declares main is a compile error.
main : List Scrollmain = [ scroll { name = "web", glyphs = [ aptPackage { name = "nginx" } ] } ]The library search path
import Foo resolves Foo.emet over a search path, first match winning.
- The entry file’s own directory.
- Each
source-directoriesentry of the nearestemet.json.
The nearest emet.json is found by walking up the entry file’s ancestor
directories. Without an emet.json, resolution is entry-directory-only.
{ "source-directories": ["lib"] }With that emet.json at the repo root, any entry resolves import Quadlet to
lib/Quadlet.emet. The entry directory keeps precedence over
source-directories.
See also
- Values and types — literals, types, user sums.
- Functions — declarations,
let,if. - Pattern matching —
caseand patterns. - Prelude — the standard-library functions.