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. The imports
themselves are fine — only a use site can be ambiguous — and the two errors
differ in how much they can tell you. A bare constructor is reported at the
reference and offers both qualified spellings. A bare type names both owners in
its message, but carries no span — it is reported at the top of the file — and
its note offers one qualified spelling and the alternative of importing only
one of the two. Renaming one of them 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.
A dot in a module name is a directory separator
A module name may be dotted, and each segment is one directory level. module Limesurvey.Database lives at Limesurvey/Database.emet, found by joining the
segments onto each search-path root in the same order as before:
lib/ Limesurvey.emet -- module Limesurvey Limesurvey/ Database.emet -- module Limesurvey.Databaseimport Limesurvey.Database exposing (containerName)import Limesurvey.Database as DbThe segments must touch: Limesurvey.Database is one name, Limesurvey . Database is not. That holds in the header, in import, and in qualified
access, where Limesurvey.Database.containerName reads as the member
containerName of the module Limesurvey.Database.
Nesting is naming and nothing else. Limesurvey.Database is an ordinary module
that happens to have a dot in its name: it sees nothing of Limesurvey that it
did not import, it gets no privileged access to it, and Limesurvey.emet need
not exist at all.
See also
- Values and types — literals, types, user sums.
- Functions — declarations,
let,if. - Pattern matching —
caseand patterns. - Prelude — the standard-library functions.