Skills gleam
Expert guidance for Gleam, the type-safe functional language that runs on the Erlang BEAM virtual machine. Helps developers build concurrent, fault-tolerant applications with Gleam's friendly syntax, exhaustive pattern matching, and access to the entire Erlang/OTP ecosystem.
git clone https://github.com/TerminalSkills/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/gleam" ~/.claude/skills/terminalskills-skills-gleam && rm -rf "$T"
skills/gleam/SKILL.md- curl piped into shell
- makes HTTP requests (curl)
Gleam — Type-Safe Language for the BEAM
Overview
Gleam, the type-safe functional language that runs on the Erlang BEAM virtual machine. Helps developers build concurrent, fault-tolerant applications with Gleam's friendly syntax, exhaustive pattern matching, and access to the entire Erlang/OTP ecosystem.
Instructions
Project Setup
# Install Gleam brew install gleam # macOS # Or: curl -fsSL https://gleam.run/install | sh # Create a new project gleam new my_app cd my_app # Run gleam run # Test gleam test # Build for production gleam build
Type-Safe Web Server with Wisp
// src/my_app/router.gleam — Type-safe HTTP routing // Gleam catches errors at compile time — no runtime crashes from typos. import gleam/http.{Get, Post, Delete} import gleam/http/request.{type Request} import gleam/http/response.{type Response} import gleam/json import gleam/result import wisp.{type Request as WispRequest, type Response as WispResponse} pub fn handle_request(req: WispRequest) -> WispResponse { case wisp.path_segments(req) { // GET /api/tasks ["api", "tasks"] -> list_tasks(req) // GET /api/tasks/:id ["api", "tasks", id] -> get_task(req, id) // POST /api/tasks ["api", "tasks"] if req.method == Post -> create_task(req) // DELETE /api/tasks/:id ["api", "tasks", id] if req.method == Delete -> delete_task(req, id) // 404 for everything else _ -> wisp.not_found() } } fn list_tasks(req: WispRequest) -> WispResponse { // Pattern matching ensures we handle every case case task_service.get_all() { Ok(tasks) -> { let body = json.array(tasks, task_to_json) wisp.json_response(json.to_string_builder(body), 200) } Error(err) -> { wisp.log_error("Failed to list tasks: " <> err) wisp.internal_server_error() } } } fn create_task(req: WispRequest) -> WispResponse { use body <- wisp.require_json(req) // Decode and validate — compiler ensures all fields are handled case decode_create_request(body) { Ok(create_req) -> { case task_service.create(create_req.title, create_req.priority) { Ok(task) -> wisp.json_response( json.to_string_builder(task_to_json(task)), 201, ) Error(err) -> wisp.unprocessable_entity() } } Error(_) -> wisp.bad_request() } }
Custom Types and Pattern Matching
// src/my_app/task.gleam — Domain types with exhaustive matching // The compiler guarantees you handle every variant — impossible to forget a case. import gleam/option.{type Option, Some, None} /// Task priority — the compiler ensures you handle all variants pub type Priority { Low Medium High Urgent } /// Task status with strict state machine semantics pub type Status { Todo InProgress Review Done Cancelled } pub type Task { Task( id: String, title: String, description: Option(String), priority: Priority, status: Status, assignee: Option(String), ) } /// Validate status transitions — not all transitions are allowed. /// The compiler ensures every combination is handled. pub fn transition(from: Status, to: Status) -> Result(Status, String) { case from, to { // Valid transitions Todo, InProgress -> Ok(InProgress) InProgress, Review -> Ok(Review) Review, Done -> Ok(Done) Review, InProgress -> Ok(InProgress) // Send back for changes // Any status can be cancelled _, Cancelled -> Ok(Cancelled) // Can't move backwards to Todo _, Todo -> Error("Cannot move back to Todo") // Can't skip steps Todo, Done -> Error("Must go through InProgress and Review first") Todo, Review -> Error("Must go through InProgress first") // Already in target state same, target if same == target -> Ok(target) // Everything else is invalid _, _ -> Error("Invalid status transition") } } /// Calculate SLA based on priority — exhaustive, no default needed pub fn sla_hours(priority: Priority) -> Int { case priority { Low -> 72 Medium -> 24 High -> 8 Urgent -> 2 } }
Concurrency with OTP Actors
// src/my_app/task_actor.gleam — Concurrent actor for task management // Gleam runs on the BEAM — millions of lightweight processes, fault-tolerant. import gleam/erlang/process.{type Subject} import gleam/otp/actor.{type Next} /// Messages the actor can receive pub type Message { GetAll(reply_to: Subject(List(Task))) Create(title: String, priority: Priority, reply_to: Subject(Result(Task, String))) UpdateStatus(id: String, status: Status, reply_to: Subject(Result(Task, String))) } /// Actor state pub type State { State(tasks: List(Task), next_id: Int) } /// Handle each message type fn handle_message(message: Message, state: State) -> Next(Message, State) { case message { GetAll(reply_to) -> { process.send(reply_to, state.tasks) actor.continue(state) } Create(title, priority, reply_to) -> { let id = "task-" <> int.to_string(state.next_id) let task = Task( id: id, title: title, description: None, priority: priority, status: Todo, assignee: None, ) process.send(reply_to, Ok(task)) actor.continue(State( tasks: [task, ..state.tasks], next_id: state.next_id + 1, )) } UpdateStatus(id, new_status, reply_to) -> { let result = update_task_status(state.tasks, id, new_status) case result { Ok(updated_tasks) -> { process.send(reply_to, Ok(find_task(updated_tasks, id))) actor.continue(State(..state, tasks: updated_tasks)) } Error(err) -> { process.send(reply_to, Error(err)) actor.continue(state) } } } } } /// Start the actor pub fn start() -> Result(Subject(Message), actor.StartError) { actor.start(State(tasks: [], next_id: 1), handle_message) }
Installation
# macOS brew install gleam # Linux curl -fsSL https://gleam.run/install | sh # Dependencies gleam add wisp # Web framework gleam add gleam_json # JSON encoding/decoding gleam add sqlight # SQLite bindings gleam add gleam_http # HTTP types
Examples
Example 1: Building a feature with Gleam
User request:
Add a real-time collaborative project setup to my React app using Gleam.
The agent installs the package, creates the component with proper Gleam initialization, implements the project setup with event handling and state management, and adds TypeScript types for the integration.
Example 2: Migrating an existing feature to Gleam
User request:
I have a basic type-safe web server with wisp built with custom code. Migrate it to use Gleam for better type-safe web server with wisp support.
The agent reads the existing implementation, maps the custom logic to Gleam's API, rewrites the components using Gleam's primitives, preserves existing behavior, and adds features only possible with Gleam (like Custom Types and Pattern Matching, Concurrency with OTP Actors).
Guidelines
- Let the compiler help — Gleam's type system catches bugs at compile time; if it compiles, it almost certainly works
- Use custom types over strings — Define
andPriority
as custom types, not strings; the compiler ensures you handle every variantStatus - Pattern matching for control flow — Replace if/else chains with pattern matching; the compiler warns about missing cases
- Result type over exceptions — Gleam has no exceptions; use
andResult(value, error)
syntax for error handlinguse - Leverage the BEAM — Gleam runs on Erlang's VM; you get fault tolerance, hot code reloading, and millions of concurrent processes for free
- Call Erlang/Elixir libraries — Use
to call any Erlang or Elixir library; the entire OTP ecosystem is available@external - Pipelines for data transformation — Use
operator for readable data pipelines:|>data |> filter |> map |> sort - Actors for state — Use OTP actors (via gleam_otp) for concurrent stateful services; each actor is isolated and crash-safe