Skip to content

Modules ​

Each .aivi file is a module. Modules import names with use and expose names with export.

Importing with use ​

aivi
use aivi.http (
    HttpError
    HttpResponse
)

use aivi.result (isOk)

Imported names become available to the rest of the file.

When you want the whole exported surface of a module, omit the import list:

aivi
use aivi.http

That form brings every exported name from aivi.http into the local file scope.

Import aliases ​

Use as when you want a local name that differs from the exported one:

aivi
use aivi.http (
    HttpError as FetchError
    HttpResponse as Response
)

type Response Text -> Bool
func isSuccess =
 ||> Ok _  -> True
 ||> Err _ -> False

Exporting names ​

A data type and its same-named constructor occupy separate type and value namespaces. One export exposes both:

aivi
type Box A = Box A
export Box

If another module imports this as use models (Box as Container), then export Container forwards both the type and the genuine constructor. Further aliases preserve the original type identity and variant name, so Container works in type annotations, constructor applications, and case patterns. Re-exporting the carrier also forwards its public class instances from their original implementation module.

The two imported namespaces must belong to the same canonical data declaration. A function that happens to return that type does not qualify as its constructor; unrelated declarations and multiple bindings in one namespace remain ambiguous.

You can export one name:

aivi
value greeting = "hello"

export greeting

Or several names together:

aivi
type Direction =
  | Up
  | Down
  | Left
  | Right

type Direction -> Direction
func opposite =
 ||> Up    -> Down
 ||> Down  -> Up
 ||> Left  -> Right
 ||> Right -> Left

value startDirection : Direction = Right

export (Direction, opposite, startDirection)

A small complete module ​

aivi
use aivi.text (
    trim
    toUpper
)

type Text -> Text -> Text
func joinLabels = left right =>
    "{left}/{right}"

value primaryLabel = "primary"
value fallbackLabel = "fallback"
value labelPair = joinLabels primaryLabel fallbackLabel

export labelPair

Typical module layout ​

A practical order is:

  1. use
  2. type / domain / class
  3. func and value
  4. signal
  5. export

That ordering is not required by the language, but it keeps modules easy to scan.

Publishing names project-wide with hoist ​

hoist is a self-declaration: a module declares that its own exports should be lifted into the project-wide namespace. Every other .aivi file in the project can then use those names directly, without any use statement.

aivi
// libs/types/ids.aivi
hoist

domain AccountId over Text
aivi
// libs/types/mail.aivi
hoist

type Message = {}

Any file in the project can then use AccountId, Message, etc. without any use statement. To show the contrast — a consuming file needs no imports at all:

aivi-fragment
// apps/ui/view.aivi — no use or hoist needed here
type AccountId -> Text -> Widget
func accountLabel = id label => ...

The stdlib prelude ​

Several commonly used standard library modules declare hoist:

aivi
// stdlib/aivi/list.aivi
hoist

Together with the compiler's ambient prelude, this makes names such as map, filter, length, getOrElse, and isOk available without an explicit import. Not every stdlib module hoists its exports: use the import shown on each module's reference page.

Kind filters ​

When you only want to publish specific kinds of exports:

aivi
hoist (func, value)

Valid kind filters: func, value, signal, type, domain, class.

Hiding specific names ​

Suppress individual names from the hoist:

aivi
hoist hiding (head, tail)

Combine kind filters and hiding:

aivi
hoist (func) hiding (foldr, foldl)

Name disambiguation ​

Ambient class methods such as map are resolved through class evidence before hoisted helpers. For other names shared by hoisted modules, the compiler uses type context to disambiguate:

aivi
type Int -> Int
func double = . * 2

type Text -> Text
func greet = "Hello, {.}"

value numbers = [1, 2, 3]
value doubled : List Int = map double numbers
value maybeName = Some "Alice"
value greeting : Option Text = map greet maybeName

If the type context is insufficient, the compiler reports an error and suggests using hiding to exclude the conflicting name from one of the hoisted modules.

Priority order ​

text
local definitions > use imports > class methods > hoisted globals > other ambient names

An explicit use aivi.list (map) selects the list helper in that file. Without that import, map remains class-polymorphic even when list helpers are hoisted.

Type names follow the same local/import/hoist priority before ambient fallback. An imported domain therefore retains its own declaration identity and carrier even when the ambient prelude supplies a domain with the same name.

use always wins over hoist for the same name, so you can override a hoisted name locally for a specific file.

Summary ​

FormMeaning
use moduleImport every exported name from this module into this file
use module (names)Import selected names into this file
use module (name as localName)Import one name under a local alias
export nameExport one name to importers
export (a, b, c)Export several names to importers
hoistPublish this module's exports to the whole project
hoist (func, value)Publish only selected kinds project-wide
hoist hiding (a, b)Publish all except named items project-wide

(c) 2026 by Andreas Herd