Skip to content
Volume G · The TensorCore.si Library

Gleam

Friendly, typed and runs on the BEAM.

Gleam is a young, statically typed functional language that runs on the Erlang virtual machine (BEAM) and can also compile to JavaScript. It brings a friendly syntax and a helpful compiler to the fault-tolerant world of Erlang and Elixir, and it reached version 1.0 in 2024.

Prologue

Why learn Gleam?

Gleam gives you Erlang's reliability with modern types and very readable code.

  • Type safe: no null, no exceptions for expected errors, exhaustive matching.
  • BEAM power: millions of lightweight processes and supervision.
  • Two targets: share code between server (Erlang) and browser (JavaScript).
Prologue

Set up and run your first program

Install Gleam and Erlang/OTP, then create a project.

terminal
gleam new hello_ai
cd hello_ai
gleam run
src/hello_ai.gleam
import gleam/io

pub fn main() {
  let name = "Ada"
  io.println("Hello, AI! Welcome to Gleam, " <> name <> ".")
}
TipJoin strings with <>. Add packages with gleam add.
Chapter 1

Values and functions

let binds immutable values. Functions are declared with fn and the last expression is returned. Ints and floats have separate operators (+ and +.).

values.gleam
import gleam/int
import gleam/io

pub fn params(layers: Int, width: Int) -> Int {
  12 * layers * width * width
}

pub fn fp16_gb(p: Float) -> Float {
  p *. 2.0 /. 1.0e9
}

pub fn main() {
  let layers = 32
  io.println("Params: " <> int.to_string(params(layers, 4096)))
}
Chapter 2

Custom types and case

Custom types list every variant. case must handle them all, and can match on values and structure.

device.gleam
pub type Device {
  Cpu
  Gpu(name: String, vram: Int)
  Tpu(version: Int)
}

pub fn describe(d: Device) -> String {
  case d {
    Cpu -> "CPU"
    Gpu(name, vram) if vram >= 80 -> "data center GPU " <> name
    Gpu(name, _) -> "GPU " <> name
    Tpu(_) -> "TPU"
  }
}
Chapter 3

Lists and pipelines

The gleam/list module covers mapping, filtering and folding. The pipe |> passes values along.

lists.gleam
import gleam/io
import gleam/list
import gleam/string

pub fn main() {
  let vram = [#("H100", 80), #("H200", 141), #("RTX 5090", 32)]
  vram
  |> list.filter(fn(p) { p.1 >= 80 })
  |> list.map(fn(p) { p.0 })
  |> string.join(", ")
  |> io.println
  let total = list.fold(vram, 0, fn(acc, p) { acc + p.1 })
  io.debug(total)
}
Chapter 4

Results and use

Operations that can fail return Result. The use expression keeps chains of fallible steps flat and readable.

results.gleam
import gleam/int
import gleam/result

pub fn fit(model_gb: Int, vram_gb: Int) -> Result(Int, String) {
  case model_gb <= vram_gb {
    True -> Ok(vram_gb - model_gb)
    False -> Error("model does not fit")
  }
}

pub fn free_after(input: String) -> Result(Int, String) {
  use gb <- result.try(int.parse(input) |> result.replace_error("not a number"))
  fit(gb, 24)
}
Chapter 5

Processes with OTP

The gleam_otp package gives typed actors on the BEAM. Each actor owns its state and handles messages one at a time.

counter.gleam
import gleam/erlang/process
import gleam/otp/actor

pub type Msg {
  Add(Int)
  Get(reply: process.Subject(Int))
}

fn handle(msg: Msg, total: Int) -> actor.Next(Msg, Int) {
  case msg {
    Add(n) -> actor.continue(total + n)
    Get(reply) -> {
      process.send(reply, total)
      actor.continue(total)
    }
  }
}

pub fn main() {
  let assert Ok(counter) = actor.start(0, handle)
  process.send(counter, Add(5))
  let total = process.call(counter, Get, 100)
  echo total
}
Part II · AI with Gleam

AI with Gleam

Gleam's strengths are reliable, concurrent services, which suits AI backends: routing requests to model servers, streaming chat responses, and handling many users at once. It calls LLM APIs with gleam_httpc and decodes responses with typed decoders, and it can use Elixir and Erlang libraries on the same VM.

The essential libraries

gleam_httpcHTTP client on Erlang.
gleam_jsonJSON encoding and decoding.
gleam/dynamic/decodeTyped decoders for untrusted data.
wisp / mistWeb framework and HTTP server.

Gradient descent from scratch

Fit y = 3x + 0.5 with lists and folds.

gradient.gleam
import gleam/int
import gleam/io
import gleam/float
import gleam/list

pub fn main() {
  let xs = list.range(0, 99) |> list.map(fn(i) { -1.0 +. 2.0 *. int.to_float(i) /. 99.0 })
  let ys = list.map(xs, fn(x) { 3.0 *. x +. 0.5 })
  let n = 100.0
  let #(w, b) =
    list.fold(list.range(1, 500), #(0.0, 0.0), fn(wb, _) {
      let #(w, b) = wb
      let errs = list.map2(xs, ys, fn(x, y) { w *. x +. b -. y })
      let gw = 2.0 *. float.sum(list.map2(errs, xs, fn(e, x) { e *. x })) /. n
      let gb = 2.0 *. float.sum(errs) /. n
      #(w -. 0.1 *. gw, b -. 0.1 *. gb)
    })
  io.println("w = " <> float.to_string(w) <> ", b = " <> float.to_string(b))
}
Part III · Build an AI agent

Call an LLM from Gleam

With gleam_http, gleam_httpc and gleam_json you can send a chat request and decode the reply into typed values. A tool-calling loop adds a tools field and repeats while tool_calls are returned.

Your goal→Model plans→Tool call→Your code runs the tool→Result back to model→Final answer
chat.gleam
import gleam/dynamic/decode
import gleam/http
import gleam/http/request
import gleam/httpc
import gleam/io
import gleam/json

pub fn main() {
  let body =
    json.object([
      #("model", json.string("llama3.1")),
      #("messages", json.preprocessed_array([
        json.object([#("role", json.string("user")),
                     #("content", json.string("Explain tensor cores in one sentence."))]),
      ])),
    ])
  let assert Ok(req) = request.to("http://localhost:11434/v1/chat/completions")
  let req =
    req
    |> request.set_method(http.Post)
    |> request.set_header("content-type", "application/json")
    |> request.set_body(json.to_string(body))
  let assert Ok(resp) = httpc.send(req)
  let content =
    decode.at(["choices"], decode.list(decode.at(["message", "content"], decode.string)))
  case json.parse(resp.body, content) {
    Ok([text, ..]) -> io.println(text)
    _ -> io.println("could not decode reply")
  }
}

Gleam's standard library changes quickly before it settles; check the docs for the versions you install.

Run it locallyInstall Ollama, run ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.
Part IV · Your path

Gleam learning roadmap

Tick items off as you go. Your progress is saved in this browser.

Beginner

Intermediate

Advanced

Part IV · Your path

Projects to build

CLI to-doA command-line task list.
JSON APIA small REST API with Wisp.
LLM gatewayRoute chat requests to local models with retries.
Reference

Gleam cheat sheet

TaskSyntax
Valuelet x = 5
Functionpub fn add(a: Int, b: Int) -> Int { a + b }
String join"a" <> "b"
Pipexs |> list.map(f)
Casecase x { Ok(v) -> v Error(_) -> 0 }
Tuple#("H100", 80)
Reference

Common mistakes

  • Ints and floats use different operators: + vs +..
  • There are no exceptions for normal errors; return and handle Result.
  • The language is young, so APIs in packages can change.
Reference

Where to go next

On TensorCore.si: Elixir volume, Erlang volume.

Keyboard: [ previous language · ] next language · / search the library