Skip to content

Search content

From F# to Gleam > 4: Lists, Dictionaries, and Eager Transformations

3 min readFrom F# to GleamPart 4
Paid order totals pass through filter and map stages before a fold combines 24 and 31 into 55.

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

On this page

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.



Previous in series
From F# to Gleam > 3: Functions, Pipelines, and Partial Application
Next in series
From F# to Gleam > 5: Option, Result, and Typed Errors