Gleam Type Aliases

A type alias gives a new name to an existing type. Aliases do not create new types — they create readable shortcuts for types that would otherwise be verbose or repeated across many function signatures.

Defining a Type Alias

type UserId = Int
type Email  = String
type Score  = Float

After these declarations, UserId and Int are completely interchangeable. The compiler treats them as identical — an alias is purely a readability tool.

Why Use Aliases


Without alias — hard to read:
──────────────────────────────────────────────────
pub fn send_invite(id: Int, address: String, points: Float) -> Bool

With aliases — intent is clear:
──────────────────────────────────────────────────
type UserId = Int
type Email  = String
type Score  = Float

pub fn send_invite(id: UserId, address: Email, points: Score) -> Bool

The alias version tells you what each argument means at a glance. The plain-type version requires you to read the documentation or count parameters to understand the purpose of each Int and String.

Aliases for Complex Types

Aliases shine when the underlying type is long or nested:

type UserMap    = Map(String, User)
type ScoreList  = List(#(String, Int))
type MaybeUser  = Option(User)
type UserResult = Result(User, String)

Without alias:
pub fn lookup(m: Map(String, User), key: String) -> Option(User)

With alias:
pub fn lookup(m: UserMap, key: String) -> MaybeUser

Aliases Are Transparent to the Compiler

The compiler sees through aliases. If a function expects Int and you pass a UserId, that is fine — they are the same type.

type UserId = Int

pub fn double(n: Int) -> Int { n * 2 }

let id: UserId = 7
let result = double(id)   // Works — UserId is Int

This transparency is a strength for convenience but also a limitation: the compiler cannot distinguish a UserId from a plain Int. If you need the compiler to enforce separation, use a custom single-field type (a wrapper type) instead.

Wrapper Types vs Aliases


Alias — transparent, no enforcement:
──────────────────────────────────────────────────
type UserId = Int
type ProductId = Int

// The compiler CANNOT tell these apart
// You can pass a ProductId where a UserId is expected

Wrapper type — opaque, enforced:
──────────────────────────────────────────────────
type UserId { UserId(value: Int) }
type ProductId { ProductId(value: Int) }

// The compiler WILL catch mixups
// UserId and ProductId are distinct types

Generic Type Aliases

Aliases can include type parameters:

type Pair(a, b)  = #(a, b)
type MaybeList(a) = Option(List(a))

let coords: Pair(Int, Int) = #(10, 20)
let numbers: MaybeList(Int) = Some([1, 2, 3])

Common Standard Library Aliases

The standard library uses several aliases you encounter often:


Common Aliases in the Ecosystem
──────────────────────────────────────────────────
Alias            │ Expands To
─────────────────┼──────────────────────────────
BitArray         │ Built-in binary data type
UtfCodepoint     │ Built-in Unicode code point

Practical Example — E-Commerce Domain

import gleam/map

type ProductId   = Int
type CustomerId  = Int
type Quantity    = Int
type Price       = Float
type Inventory   = Map(ProductId, Quantity)

pub fn check_stock(inv: Inventory, product: ProductId) -> Quantity {
  case map.get(inv, product) {
    Ok(qty) -> qty
    Error(_) -> 0
  }
}

pub fn can_fulfill(inv: Inventory, product: ProductId, needed: Quantity) -> Bool {
  check_stock(inv, product) >= needed
}

Every function signature reads like plain English. check_stock takes an Inventory and a ProductId — you immediately understand what each parameter represents without looking up documentation.

When to Use Aliases


Decision Guide
──────────────────────────────────────────────────
Use alias when:
  ✓ A type appears in many function signatures
  ✓ The raw type name obscures intent (Int, String)
  ✓ A complex generic type repeats everywhere
  ✓ You want to document purpose through naming

Use wrapper type when:
  ✓ You need the compiler to prevent mixing types
  ✓ The concept is distinct enough to deserve a type

Key Points


Type Alias Essentials
──────────────────────────────────────────────────
1. Declare with: type AliasName = ExistingType
2. Alias and original type are interchangeable
3. Improves readability — no runtime cost
4. Does NOT enforce type separation
5. Supports type parameters: type Pair(a) = #(a, a)
6. Use wrapper types when enforcement matters

Type aliases are a zero-cost documentation tool. They cost nothing at runtime and add significant clarity at read time. Use them freely to give meaningful names to raw types and to tame complex generic signatures.

Leave a Comment

Your email address will not be published. Required fields are marked *