Results, Options, and Error Handling

Errors as Values

Parsing and Dividing Safely

i
fn parse_int(s: String) -> Result(Int, String) {
  case int.parse(s) {
    Ok(n) -> Ok(n)
    Error(_) -> Error("not an integer: " <> s)
  }
}
i
fn safe_divide(a: Int, b: Int) -> Result(Int, String) {
  case b {
    0 -> Error("division by zero")
    _ -> Ok(a / b)
  }
}
i
Ok(42)
Error("not an integer: not a number")
Ok(5)
Error("division by zero")

Transforming Results

i
  io.println(string.inspect(ok_int(42) |> result.map(fn(x) { x + 1 })))
  io.println(string.inspect(error_str("oops") |> result.map(fn(x) { x + 1 })))
i
Ok(43)
Error("oops")

The Option Type

i
fn option_inspect(opt: option.Option(Int)) -> String {
  case opt {
    option.Some(n) -> "got " <> int.to_string(n)
    option.None -> "nothing"
  }
}
i
fn first_positive(nums: List(Int)) -> option.Option(Int) {
  case nums {
    [] -> option.None
    [x, ..] if x > 0 -> option.Some(x)
    [_, ..rest] -> first_positive(rest)
  }
}
i
"got 42"
"nothing"
Some(99)
None
Some(3)
None

Guidelines for Handling Errors

i
  let assert Ok(n) = int.parse("42")
  io.println("parsed: " <> int.to_string(n))
i
parsed: 42

A Note on Assertions

Check Understanding

When should you use Option instead of Result?

Exercises

Safe division (5 minutes)

Write safe_divide(a: Int, b: Int) -> Result(Int, String) that returns Error("division by zero") when b is 0 and Ok(a / b) otherwise.

Mapping over results (5 minutes)

Write a function that takes a List(String) and returns a List(Int) containing only the elements that parse successfully as integers. Use int.parse, list.filter_map, or a combination of list.map and list.filter.

First matching element (10 minutes)

Write find(items: List(a), pred: fn(a) -> Bool) -> Option(a) that returns Some(x) for the first element where pred(x) is True, or None if no element matches. Write it using recursion with a case expression.