F#‘s List habits transfer well to Gleam: immutable linked lists, pattern matching, and small transformations. The habit to reconsider is treating every pipeline as a lazy seq.
Table of contents
Filter, Map, Fold
Suppose we only want the total of paid orders:
type Order = { Total: int; Paid: bool }
let paidTotal orders =
orders
|> List.filter (fun order -> order.Paid)
|> List.map (fun order -> order.Total)
|> List.fold (+) 0
let orders =
[ { Total = 24; Paid = true }
{ Total = 18; Paid = false }
{ Total = 31; Paid = true } ]
assert (paidTotal orders = 55)
assert (paidTotal [] = 0)scratch.fsx
The Gleam version keeps the stages but moves the collection to the 1st argument:
import gleam/dict
import gleam/list
pub type Order {
Order(total: Int, paid: Bool)
}
fn paid_total(orders: List(Order)) -> Int {
orders
|> list.filter(fn(order) { order.paid })
|> list.map(fn(order) { order.total })
|> list.fold(0, fn(total, amount) { total + amount })
}src/learning_gleam.gleam
list.fold receives the list, the initial accumulator, and the callback. Its callback receives the accumulator before the current element. Starting at 0 also gives the empty list a sensible result.
A List Isn’t an Array
Adding at the front with [new_value, ..existing] is cheap. Repeatedly appending at the end copies the left-hand list, so don’t build a large result one append at a time. Prepend and reverse, or use a standard library transformation.
The same [first, ..rest] shape works in case expressions. Prefer matching or traversing over repeatedly asking for a list’s length or indexing into it.
Dictionaries Return Checked Lookups
F#‘s immutable Map has a close practical counterpart in gleam/dict. The lookup result differs:
let inventory = Map [ "apples", 2; "pears", 3 ]
let restocked = inventory |> Map.add "apples" 6
assert (Map.tryFind "apples" inventory = Some 2)
assert (Map.tryFind "apples" restocked = Some 6)
assert (Map.tryFind "oranges" inventory = None)scratch.fsx
Gleam uses #(key, value) for each pair. dict.get returns Ok(value) or Error(Nil), not an Option. We’ll look at both types in the next article.
Neither a dictionary’s traversal order nor its implementation should be assumed to match F#‘s ordered Map. Sort explicitly if the output needs a stable presentation order.
Try It
Put the Gleam blocks in src/learning_gleam.gleam, then run gleam run:
pub fn main() -> Nil {
let orders = [Order(24, True), Order(18, False), Order(31, True)]
let inventory = dict.from_list([#("apples", 2), #("pears", 3)])
let restocked = dict.insert(inventory, "apples", 6)
assert paid_total(orders) == 55
assert paid_total([]) == 0
assert dict.get(inventory, "apples") == Ok(2)
assert dict.get(restocked, "apples") == Ok(6)
assert dict.get(inventory, "oranges") == Error(Nil)
}src/learning_gleam.gleam
Try rewriting paid_total as one fold if you want to avoid the intermediate filtered and mapped lists. Keep the staged version when its readability matters more than those allocations.
Don’t translate Seq.initInfinite into an eagerly constructed Gleam list. list.take can’t rescue a producer that never finishes constructing its input. Use bounded recursion or a suitable streaming package when the problem actually needs incremental production.
Further reading: gleam/list and gleam/dict.