Functional programming in Gleam #
Gleam is a functional language where values are immutable and functions are the main way to organize behavior.[1] That affects how Gleam programs are written and how a compiler represents them.
In an imperative language, a loop might update a variable again and again. Gleam code often builds a new value from an old one:
let name = "Lucy"
let greeting = "Hello, " <> name
greeting
The name greeting is bound to a value. The value does not change. If the same
variable name is reused later, it is a new binding that shadows the old one; the
old value has not been mutated.[2]
Expressions #
Gleam is expression-oriented. Blocks and function bodies evaluate expressions in order, and the final expression is the result.[3]
pub fn greeting(name: String) -> String {
let prefix = "Hello, "
prefix <> name
}
There is no return keyword in the ordinary function body. The compiler can
treat the body as an expression tree whose final value becomes the function
result.
Functions as values #
Functions are values in Gleam. They can be assigned to variables, passed to other functions, and returned like other values.[4]
fn twice(value: Int, change: fn(Int) -> Int) -> Int {
change(change(value))
}
pub fn main() {
twice(1, fn(x) { x + 1 })
}
The anonymous function fn(x) { x + 1 } can capture names from the scope where
it is defined, making it a closure.[5] For a compiler, that means a function
value may need a code pointer plus the captured values that the function body
can use later.
Pipelines #
Functional code often transforms data through a series of small functions. The
pipe operator, |>, sends the value on the left into the function call on the
right.[6]
import gleam/int
import gleam/string
pub fn label(number: Int) -> String {
number
|> int.to_string
|> string.append(" items")
}
The pipeline is ordinary function application with syntax that makes the flow of data easier to read from top to bottom.
Recursion and lists #
Gleam does not have traditional loops. Iteration is commonly written with
recursion or with functions from modules such as gleam/list.[7]
fn count(items: List(a)) -> Int {
case items {
[] -> 0
[_, ..rest] -> 1 + count(rest)
}
}
For long-running recursive functions, tail calls matter. Gleam supports tail call optimisation, allowing a function call in tail position to reuse the current stack frame.[8]
The standard library often gives the clearer version:
import gleam/list
pub fn double_all(numbers: List(Int)) -> List(Int) {
list.map(numbers, fn(n) { n * 2 })
}
Data shapes #
Functional programs often model data with a small set of explicit shapes. Gleam custom types make those shapes visible:
pub type Login {
Anonymous
SignedIn(name: String)
}
Pattern matching then handles each shape:
fn display(login: Login) -> String {
case login {
Anonymous -> "guest"
SignedIn(name) -> name
}
}
This style is valuable to a compiler because the possible cases are part of the program structure. Name resolution, type checking, exhaustiveness checking, and code generation can all use that structure.
Gleam FAQ, "Does Gleam have mutable state?": https://gleam.run/frequently-asked-questions ↩︎
Gleam Language Tour, "Assignments": https://tour.gleam.run/basics/assignments/ ↩︎
Gleam Language Tour, "Functions": https://tour.gleam.run/functions/functions/ ↩︎
Gleam Language Tour, "Higher order functions": https://tour.gleam.run/functions/higher-order-functions/ ↩︎
Gleam Language Tour, "Anonymous functions": https://tour.gleam.run/functions/anonymous-functions/ ↩︎
Gleam Language Tour, "Pipelines": https://tour.gleam.run/functions/pipelines/ ↩︎
Gleam Language Tour, "List recursion": https://tour.gleam.run/flow-control/list-recursion/ ↩︎
Gleam Language Tour, "Tail calls": https://tour.gleam.run/flow-control/tail-calls/ ↩︎