Functions, Pattern Matching, Lists, and Pipelines

Defining Functions

fn add(a: Int, b: Int) -> Int {
  a + b
}

Pattern Matching

i
type Triangle {
  Equilateral
  Isosceles
  Scalene
}

fn classify_triangle(a: Int, b: Int, c: Int) -> Triangle {
  case a, b, c {
    x, y, z if x == y && y == z -> Equilateral
    x, y, _ if x == y -> Isosceles
    _, y, z if y == z -> Isosceles
    x, _, z if x == z -> Isosceles
    _, _, _ -> Scalene
  }
}
i
fn is_palindrome(s: String) -> Bool {
  let chars = string.to_graphemes(s)
  chars == list.reverse(chars)
}

Recursion Instead of Loops

i
fn length(lst: List(a)) -> Int {
  case lst {
    [] -> 0
    [_, ..rest] -> 1 + length(rest)
  }
}

Tail Recursion

i
fn factorial(n: Int) -> Int {
  factorial_tail(n, 1)
}

fn factorial_tail(n: Int, acc: Int) -> Int {
  case n {
    0 -> acc
    _ -> factorial_tail(n - 1, acc * n)
  }
}
i
fn take(lst: List(a), n: Int) -> List(a) {
  case lst, n {
    _, 0 -> []
    [], _ -> []
    [x, ..rest], _ -> [x, ..take(rest, n - 1)]
  }
}
i
length nums = 5
length [] = 0
factorial(5) = 120
factorial(0) = 1
take 3 = [1, 2, 3]
take 10 = [1, 2, 3, 4, 5]

Lists and Higher-Order Functions

list.map([1, 2, 3], fn(x) { x * 2 })
// => [2, 4, 6]

The Pipeline Operator

i
  let result =
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    |> list.map(fn(x) { x * 2 })
    |> list.filter(fn(x) { x > 10 })
    |> list.fold(0, fn(acc, x) { acc + x })

  io.println("pipeline result is " <> string.inspect(result))
i
pipeline result is 80
reduce(lambda a, x: a+x, filter(lambda x: x>10, map(lambda x: x*2, lst)), 0)

Check Understanding

What does "exhaustive" mean for a case expression?

An exhaustive match covers every possible value of the type being matched. If you write a case on a Triangle but only handle Equilateral and Isosceles, the compiler reports an error because Scalene is not covered. This prevents a common class of bug where a new variant is added to a type and existing match expressions silently fall through to a catch-all.

When should you use list.fold instead of list.map or list.filter?

Use list.fold when you need to combine all elements of a list into a single value, like a sum or a product. list.map transforms each element independently; list.filter selects a subset of elements. Many operations that would use a for loop in Python can be expressed with one of these three functions in Gleam.

Exercises

Recursive sum (5 minutes)

Write sum(lst: List(Int)) -> Int using tail recursion with an accumulator. Verify that sum([1, 2, 3, 4, 5]) returns 15 and sum([]) returns 0.

Palindrome checker (10 minutes)

Write is_palindrome(s: String) -> Bool using string.to_graphemes and list.reverse. Test it with "racecar", "gleam", and at least one string with a non-ASCII character.

Pipeline challenge (10 minutes)

Use a single pipeline to take the list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], keep only odd numbers, square each one, and sum the results. The answer should be 165.

Triangle classifier (10 minutes)

Extend the classify_triangle function to also return a label for right triangles. A triangle with sides a, b, c (where c is the longest side) is a right triangle if a*a + b*b == c*c. Determine the correct arm ordering so that an equilateral triangle is never labelled as right.