Gleam Type System
Use when gleam's type system including algebraic data types, custom types, pattern matching, generic types, type inference, opaque types, exhaustive checking, and functional error handling for building type-safe Erlang VM applications.
What this skill does
# Gleam Type System
## Introduction
Gleam is a statically-typed functional language that compiles to Erlang and
JavaScript, bringing modern type safety to the BEAM ecosystem. Its type system
prevents entire categories of runtime errors while maintaining the concurrency
and fault-tolerance benefits of the Erlang VM.
The type system features algebraic data types, parametric polymorphism, type
inference, exhaustive pattern matching, and no null values. Every value is typed,
and the compiler enforces type safety at compile time, eliminating common bugs
before code runs.
This skill covers custom types and ADTs, pattern matching, generic types, Result
and Option types, type aliases, opaque types, type inference, and patterns for
type-safe error handling on the BEAM.
## Custom Types and Records
Custom types define structured data with named fields, providing type-safe
access and pattern matching.
```gleam
// Simple custom type (record)
pub type User {
User(name: String, age: Int, email: String)
}
// Creating instances
pub fn create_user() -> User {
User(name: "Alice", age: 30, email: "[email protected]")
}
// Accessing fields
pub fn get_user_name(user: User) -> String {
user.name
}
pub fn get_user_age(user: User) -> Int {
user.age
}
// Updating records (immutable)
pub fn birthday(user: User) -> User {
User(..user, age: user.age + 1)
}
pub fn change_email(user: User, new_email: String) -> User {
User(..user, email: new_email)
}
// Multiple constructors
pub type Shape {
Circle(radius: Float)
Rectangle(width: Float, height: Float)
Triangle(base: Float, height: Float)
}
pub fn area(shape: Shape) -> Float {
case shape {
Circle(radius) -> 3.14159 *. radius *. radius
Rectangle(width, height) -> width *. height
Triangle(base, height) -> base *. height /. 2.0
}
}
// Tuple structs (unlabeled fields)
pub type Point {
Point(Float, Float)
}
pub fn distance(p1: Point, p2: Point) -> Float {
let Point(x1, y1) = p1
let Point(x2, y2) = p2
let dx = x2 -. x1
let dy = y2 -. y1
float.square_root(dx *. dx +. dy *. dy)
}
// Nested custom types
pub type Address {
Address(street: String, city: String, zip: String)
}
pub type Person {
Person(name: String, age: Int, address: Address)
}
pub fn get_city(person: Person) -> String {
person.address.city
}
// Generic custom types
pub type Box(a) {
Box(value: a)
}
pub fn box_map(box: Box(a), f: fn(a) -> b) -> Box(b) {
Box(value: f(box.value))
}
pub fn unbox(box: Box(a)) -> a {
box.value
}
// Recursive types
pub type Tree(a) {
Leaf(value: a)
Branch(left: Tree(a), right: Tree(a))
}
pub fn tree_depth(tree: Tree(a)) -> Int {
case tree {
Leaf(_) -> 1
Branch(left, right) -> 1 + int.max(tree_depth(left), tree_depth(right))
}
}
// Phantom types for type-safe APIs
pub type Validated
pub type Unvalidated
pub type Email(state) {
Email(value: String)
}
pub fn create_email(value: String) -> Email(Unvalidated) {
Email(value: value)
}
pub fn validate_email(email: Email(Unvalidated)) ->
Result(Email(Validated), String) {
case string.contains(email.value, "@") {
True -> Ok(Email(value: email.value))
False -> Error("Invalid email format")
}
}
pub fn send_email(email: Email(Validated)) -> Nil {
// Only validated emails can be sent
io.println("Sending email to: " <> email.value)
}
```
Custom types provide named, type-safe data structures with exhaustive pattern
matching guarantees.
## Algebraic Data Types
ADTs model data with multiple variants, enabling exhaustive pattern matching and
making invalid states unrepresentable.
```gleam
// Sum type (enum)
pub type Status {
Pending
Approved
Rejected
}
pub fn status_to_string(status: Status) -> String {
case status {
Pending -> "Pending"
Approved -> "Approved"
Rejected -> "Rejected"
}
}
// Result type (built-in ADT)
pub type Result(ok, error) {
Ok(ok)
Error(error)
}
pub fn parse_int(str: String) -> Result(Int, String) {
case int.parse(str) {
Ok(n) -> Ok(n)
Error(_) -> Error("Not a valid integer")
}
}
pub fn handle_result(result: Result(Int, String)) -> String {
case result {
Ok(n) -> "Got number: " <> int.to_string(n)
Error(msg) -> "Error: " <> msg
}
}
// Option type pattern
pub type Option(a) {
Some(a)
None
}
pub fn find_user(id: Int) -> Option(User) {
case id {
1 -> Some(User(name: "Alice", age: 30, email: "[email protected]"))
_ -> None
}
}
pub fn option_map(opt: Option(a), f: fn(a) -> b) -> Option(b) {
case opt {
Some(value) -> Some(f(value))
None -> None
}
}
pub fn option_unwrap_or(opt: Option(a), default: a) -> a {
case opt {
Some(value) -> value
None -> default
}
}
// Complex ADTs
pub type HttpResponse {
Ok200(body: String)
Created201(body: String, location: String)
BadRequest400(message: String)
NotFound404
ServerError500(message: String)
}
pub fn handle_response(response: HttpResponse) -> String {
case response {
Ok200(body) -> "Success: " <> body
Created201(body, location) -> "Created at " <> location <> ": " <> body
BadRequest400(message) -> "Bad request: " <> message
NotFound404 -> "Resource not found"
ServerError500(message) -> "Server error: " <> message
}
}
// Linked list ADT
pub type List(a) {
Nil
Cons(head: a, tail: List(a))
}
pub fn list_length(list: List(a)) -> Int {
case list {
Nil -> 0
Cons(_, tail) -> 1 + list_length(tail)
}
}
pub fn list_map(list: List(a), f: fn(a) -> b) -> List(b) {
case list {
Nil -> Nil
Cons(head, tail) -> Cons(f(head), list_map(tail, f))
}
}
// Either type
pub type Either(left, right) {
Left(left)
Right(right)
}
pub fn partition_either(list: List(Either(a, b))) -> #(List(a), List(b)) {
case list {
Nil -> #(Nil, Nil)
Cons(Left(a), tail) -> {
let #(lefts, rights) = partition_either(tail)
#(Cons(a, lefts), rights)
}
Cons(Right(b), tail) -> {
let #(lefts, rights) = partition_either(tail)
#(lefts, Cons(b, rights))
}
}
}
// State machine with ADTs
pub type ConnectionState {
Disconnected
Connecting(attempt: Int)
Connected(session_id: String)
Disconnecting
}
pub fn handle_connect_event(state: ConnectionState) -> ConnectionState {
case state {
Disconnected -> Connecting(attempt: 1)
Connecting(attempt) if attempt < 3 -> Connecting(attempt: attempt + 1)
Connecting(_) -> Disconnected
Connected(_) -> state
Disconnecting -> state
}
}
// Expression tree ADT
pub type Expr {
Number(Float)
Add(left: Expr, right: Expr)
Subtract(left: Expr, right: Expr)
Multiply(left: Expr, right: Expr)
Divide(left: Expr, right: Expr)
}
pub fn evaluate(expr: Expr) -> Result(Float, String) {
case expr {
Number(n) -> Ok(n)
Add(left, right) -> {
use l <- result.try(evaluate(left))
use r <- result.try(evaluate(right))
Ok(l +. r)
}
Subtract(left, right) -> {
use l <- result.try(evaluate(left))
use r <- result.try(evaluate(right))
Ok(l -. r)
}
Multiply(left, right) -> {
use l <- result.try(evaluate(left))
use r <- result.try(evaluate(right))
Ok(l *. r)
}
Divide(left, right) -> {
use l <- result.try(evaluate(left))
use r <- result.try(evaluate(right))
case r {
0.0 -> Error("Division by zero")
_ -> Ok(l /. r)
}
}
}
}
```
ADTs enable type-safe modeling of complex domain logic with compiler-verified
exhaustiveness.
## Pattern Matching
Pattern matching provides exhaustive, type-safe conditional logic with
destructuring capabilities.
```gleam
// Basic pattern matching
pub fn describe_number(n: Int) -> String {
case n {
0 -> "zero"
1 -> "one"
2 -> "two"
_ -> "many"
}
}
// Pattern matching with guards
pub fn classify_age(age: Int) -> String {
case age {
n if n < 0 -> "Invalid"
n if n < 13 -> "Child"
n if n < 20 -> "Teen"
n if n < 65 -> "Adult"
_ -> "Senior"
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.