funtil

Types

This type is used to represent a value that can never happen. What does that mean exactly?

  • A Bool is a type that has two values: True and False.
  • Nil is a type that has one value: Nil.
  • Never is a type that has zero values: it’s impossible to construct!

Why this type is useful is a bit of a mind-bender, but it’s a useful tool to have in your toolbox. For example, if you have a function that returns a Result(Int, Never) then you know that it will always return an Int and it is safe to use let assert to unwrap the value.

pub opaque type Never

Functions

pub fn fix(f: fn(fn(a) -> b, a) -> b) -> fn(a) -> b

Gleam’s type system does not support recursive let-bound functions, even though they are theoretically possible. The fix combinator is a sneaky way around this limitation by making the recursive function a parameter of itself.

Sound a bit too magical? Let’s first take a look at what happens if we try to write a recursive let-bound function in Gleam:

pub fn example() {
  let factorial = fn(x) {
      case x {
        0 -> 1
        x -> x * factorial(x - 1)
              // ^^^^^^^^^ The name `factorial` is not in scope here.
      }
    }

  fact(5)
  |> should.equal(120)
}

We get a compile error because the name factorial is not in scope inside the function body. What does it look like if we try to use fix?

import funtil.{fix}

pub fn example() {
  let factorial =
    fix(fn(factorial, x) {
      case x {
        0 -> 1
        x -> x * factorial(x - 1)
      }
    })

  fact(5)
  |> should.equal(120)
}

🚨 Gleam is designed with this limitation to encourage you to pull things out of let bindings when they get too complex. If you find yourself reaching for fix, consider if there’s a clearer way to solve your problem.

pub fn fix2(f: fn(fn(a, b) -> c, a, b) -> c) -> fn(a, b) -> c

A version of the fix util for functions that take two arguments.

🚨 Gleam is designed with this limitation to encourage you to pull things out of let bindings when they get too complex. If you find yourself reaching for fix2, consider if there’s a clearer way to solve your problem.

pub fn fix3(f: fn(fn(a, b, c) -> d, a, b, c) -> d) -> fn(a, b, c) ->
  d

A version of the fix util for functions that take three arguments.

🚨 Gleam is designed with this limitation to encourage you to pull things out of let bindings when they get too complex. If you find yourself reaching for fix3, consider if there’s a clearer way to solve your problem.

pub fn never(val: Never) -> a

If you’ve got a Never somewhere in a type it can be a bit of a problem if you need something else. Just like Never is a type that can never be constructed, never is a function that can never be called.

To take our Result(Int, Never) example from above, what if we want to pass that value into a function that expects a Result(Int, String)? As it stands, the types don’t match up, but because we know that we can never have an error, we can use never to pretend to convert it into a String:

import funtil.{Never, never}
import gleam/io
import gleam/result

fn log_error(result: Result(a, String)) -> Result(a, String) {
  case result {
    Ok(a) -> Nil
    Error(message) -> io.println(message)
  }

  result
}

fn example() {
  let val: Result(Int, Never) = Ok(42)

  val
  |> result.map_error(never)
  |> log_error
}
pub fn void(_: a) -> Nil

Take any value and replace it with Nil. This can be a nicer way of using value-producing functions in places where you only care about their side effects.

Search Document