Documentation / Aliases

Aliases

An alias gives an existing type, dependency or payload field a local name. It doesn't create a value, conversion or new nominal type.

Moth keeps as narrower than languages with broad renaming or casting syntax. The surrounding declaration always determines what gets renamed.

Choose the explanation level: Type aliases

Type aliases

A type alias gives an existing type a clearer name. It does not create a new type.

When to use a type alias

Use a type alias when a type annotation would be repetitive or when a name carries domain meaning.

UserId as Int
HtmlFragment as String
RouteParts as {String}

Aliases are transparent

An alias is another spelling of the same type.

UserId as Int

id UserId = 42
raw Int = id

id has type UserId, but it is also an Int. You can pass it anywhere an Int is expected.

UserId as Int makes annotations clearer, but it does not stop an ordinary Int from being used as UserId.

Alias versus struct

A struct creates a brand new type with its own fields and constructor. An alias does not.

UserId as Int

User = |
    id Int,
|

UserId is Int. User is a separate type.

One collection alias

An alias can name a collection type.

Names as {String}

names Names = {"Priya", "Rob"}

A type alias is a compile-time name for an existing type. It introduces no runtime value and no new semantic type identity.

Definition and syntax

UserId as Int
Names as {String}
MaybeName as String?
StringBox as Box of String

The syntax is AliasName as ExistingType. Alias names follow type-name casing. The target may be any valid type expression that resolves to a concrete semantic type.

Exact contract

  • A type alias is a compile-time name for an existing type.
  • Aliases are transparent: an alias and its target denote the same TypeId.
  • Aliases do not create nominal identity.
  • Alias and target values are type-compatible in both directions.
  • Aliases are valid in type positions such as parameters, fields, returns and local annotations.
  • Aliases are not ordinary values. They cannot be assigned, passed, copied or used in expression position.
  • as in a type-alias declaration is not a cast.

Valid targets

An alias target may be:

  • a builtin scalar such as Int, Float, Bool, Char or String
  • a struct or choice declared in the same file
  • an option such as String?
  • a growable collection such as {String}
  • a fixed collection such as {4 Int}
  • a hashmap such as {String = Int}
  • a fully concrete generic instance such as Box of String
  • a dependency-bound type, dependency-bound type alias or external package type

Concrete generic aliases are supported. Parameterized aliases and partial generic application are outside the current language scope.

Transparency

UserId as Int

id UserId = 42
raw Int = id

Because UserId is transparent, assigning a UserId value to an Int binding is valid. The reverse is also valid: an Int value can be used where UserId is expected.

Contrast with a struct

UserId as Int

User = |
    id Int,
|

UserId is another name for Int. User is a distinct nominal struct type with its own constructor and fields. Use a struct when the domain concept needs its own identity. Use an alias when only the annotation name matters.

Alias versus nominal domain type

An alias improves spelling and readability, but it doesn't isolate a domain value from its target type. UserId as Int still allows an ordinary Int where UserId is expected. Use a wrapper struct today when distinct nominal identity matters.

Moth has no accepted primitive-backed nominal wrapper syntax. Such a wrapper remains an open design question and any future mechanism would stay separate from transparent as aliases.

The accepted alias contract includes external package types. The current compiler still rejects aliases to external opaque types. See @../progress (Progress matrix) for current support.

Empty literals and aliases

An alias can provide the explicit element type needed by an empty collection or map literal.

Names as {String}

names Names = {}

Alias chains

Aliases may chain through other aliases. The chain remains transparent and resolves to the final target type.

Name as String
Names as {Name}

names Names = {"Priya"}

Cycles

Alias cycles are invalid. The compiler reports a circular dependency.

Aliases are not constructors

An alias does not create a new constructor. The alias name is not callable even when the alias targets a constructible nominal type. Construct the target type using its canonical nominal name.

For struct aliases, only the struct's canonical name constructs:

Person = |
    name String,
|

MainPerson as Person

-- Valid: construct with the canonical name
person MainPerson = Person("Priya")

-- Error: MainPerson is a type, not a constructor
-- person MainPerson = MainPerson("Priya")

For choice aliases, only the choice's canonical name constructs variants:

Status ::
    Ready,
    Failed | message String |,
;

State as Status

-- Valid: construct with the canonical name
ready State = Status::Ready
failed State = Status::Failed("bad")

-- Error: State is a type, not a constructor
-- ready State = State::Ready

Aliases to scalar or otherwise non-constructible targets are not callable for the same reason: the alias name is a type, not a value.

Visibility and module public surfaces

Type aliases follow ordinary module visibility rules. A type alias declared in a source file is dependency-bindable by other files in the same module. Cross-module visibility is controlled by the module root's explicit export: surface. The normal module-root filename itself has no semantic role. A public re-export may expose a dependency-bound alias under a chosen public name. That operation is not the same as an ordinary file-local dependency alias.

Related concepts

  • For concrete generic application see Generics.
  • For dependency aliasing see the next section.
  • For project visibility and module roots see Project Structure and Packages.
Choose the explanation level: Dependency aliases

Dependency aliases

Dependency aliases rename something you bind so it fits better in the current file.

Two packages called render

Suppose two packages both export a function named render. You cannot bind both as render because names must not collide.

@blog render as render_blog
@docs render as render_docs

blog_html = render_blog()
docs_html = render_docs()

Namespace aliases

You can also rename a whole namespace.

@core/math as math

value = math.sin(1.0)

File-local scope

A dependency alias is visible only in the file where it is written. It does not change the exported name in the source file, and it is not automatically re-exported.

No shadowing

An alias cannot hide an existing name. If render is already declared in the file, @other render is an error. Pick a different local name instead.

Dependency aliases rename a bound symbol for local use inside one file. They are distinct from type aliases and from public module re-export names.

Definition and syntax

A clause-level namespace alias changes the local name of a bound namespace:

@core/math as math

An entry alias changes the local name of one bound symbol:

@core/math sin as sine, PI as CIRCLE_PI

Exact contract

  • A clause-level namespace alias changes the namespace name visible in one file.
  • An entry alias changes one bound symbol visible in one file.
  • The source declaration keeps its canonical exported identity.
  • Ordinary dependency aliases are file-local.
  • Ordinary dependency aliases are not automatically re-exported.
  • Aliases work for every bindable symbol category the dependency surface supports: functions, constants, structs, choices, type aliases and external package members.
  • External package aliases follow the same local collision policy as source dependency clauses.
  • Each selected entry is renamed separately.
  • A clause-level as and direct selections are mutually exclusive. There is no filtered namespace form.

Collision rules

Dependency aliases must not collide with any visible name in the same file, including:

  • same-file declarations
  • other dependency clauses
  • other aliases
  • type aliases
  • builtins
  • preluded names

Aliases do not shadow an existing visible name. A collision is a compile error.

render || -> String:
    return "local"
;

-- Error: render is already visible in this file
@other render

Resolve the collision by renaming the dependency.

@other render as render_other

Leading-case warnings

An alias whose leading case differs from the bound symbol's leading case produces a warning. The warning is intentionally simple: it catches obvious type-versus-value naming drift without trying to classify every naming convention.

-- Warning
@core/math sin as Sine

-- Warning
@core/math PI as pi

Namespace aliases

Use a clause-level namespace alias when the bound path would be awkward to repeat or when two bound namespaces share a basename.

@core/math as math
@core/text as text

value = math.sin(1.0)
length = text.length("hello")

Entry aliases

Use entry aliases when two packages expose the same name.

@blog render as render_blog
@docs render as render_docs

blog_html = render_blog()
docs_html = render_docs()

A clause cannot use a single alias for a direct-selection list. Clause-level as and direct selections are mutually exclusive.

-- Error
@components as Ui Button, Card

-- Valid namespace alias
@components as Ui

Local and public-name distinction

An ordinary dependency alias is private to the file that declares it. A public name chosen in a module root's export surface is a different operation. Cross-module visibility is controlled by the module root's explicit export: surface. The normal module-root filename itself has no semantic role. The current export syntax is owned by Project Structure and Packages. Do not treat a local dependency alias as a public API rename.

-- Ordinary local dependency alias
@models User as ModelUser

The local ModelUser is visible only in this file. A public rename belongs inside the module root's current explicit export: block. See Project Structure and Packages for the exact current form.

Dependency aliases with type aliases

A type alias may target a bound type alias.

@models User as ExternalUser

CurrentUser as ExternalUser

This combines a file-local dependency rename with a domain-specific type alias.

Related concepts

  • For type aliases see the previous section.
  • For payload capture aliases see the next section.
  • For dependency syntax and module roots see Packages and Project Structure.
Choose the explanation level: Payload capture aliases

Payload capture aliases

When you match a choice with a payload, the field name becomes the local variable inside that arm. Sometimes that name collides with another variable or feels wrong in context.

Rename one payload field

Response ::
    Err | message String |,
    Success | value String |,
;

response = Response::Err("oops")

if response is:
    Err(message as error_message) => io.line(error_message)
    Success(value as body) => io.line(body)
;

message as error_message means: take the payload field called message and bind it to error_message in this arm only.

Why rename?

message = "outer message"

if response is:
    Err(message as failure_message) => io.line(failure_message)
    else => io.line(message)
;

Without the alias, the capture would collide with the visible outer message and be rejected. The alias keeps both names distinct.

A payload capture alias renames a choice payload field inside one match arm. It is the third narrow as surface in Moth.

Definition and syntax

if response is:
    Err(message as error_message) => io.line(error_message)
    Success(value as body) => io.line(body)
;

The name before as is the declared payload field name. The name after as is an arm-local binding.

Exact contract

  • The name before as must be the declared payload field name.
  • The name after as is an arm-local binding.
  • The alias does not rename the field.
  • The alias does not leak into other arms.
  • The alias is visible in the arm guard and body.
  • Without as, the field name becomes the local binding.
  • The local binding follows naming and no-shadowing rules.
  • Duplicate local bindings in the same arm are invalid.
  • Payload capture aliases do not create value aliases.
  • value as Type is not cast syntax.
  • Expression aliases are unsupported.

Avoiding outer-name collisions

Use a capture alias when the field name would collide with an existing variable or when the local meaning is clearer.

message = "outer message"

if response is:
    Err(message as error_message) => io.line(error_message)
    else => io.line(message)
;

Inside the first arm, error_message holds the payload. In the else arm, message still refers to the outer binding.

Related concepts

  • For choice declarations and variants see Choices.
  • For pattern matching and exhaustiveness see Branching.