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

Values

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.
      }
    }

  assert fact(5) == 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

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

  assert fact(5) == 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 then(
  first: fn(a) -> b,
  do second: fn(b) -> c,
) -> fn(a) -> c

Compose two functions together, where the output of the first function is passed as the input to the second function. This is known as composition and can be a way to write code in a “point-free” style where you don’t explicitly mention function arguments.

import funtil
import gleam/list
import gleam/string

fn example() {
  let shouty_names =
    list.map(
      ["yoshie", "danielle", "marniek"],
      string.uppercase |> funtil.then(string.append(_, "!"))
    )

  assert shouty_names == ["YOSHIE!", "DANIELLE!", "MARNIEK!"]
}

🚨 Gleam intentionally doesn’t have an operator for function composition. In other languages this is often represented as . or >>. Point-free programming can be a useful tool but it can also make code harder to understand, hold with care!

pub fn void(arg: 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