src/funtil.gleam

// 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 {
  JustOneMore(Never)
}

// MANIPULATIONS ---------------------------------------------------------------

/// 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`](#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`:
///
/// ```gleam
/// 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 never(val: Never) -> a {
  case val {
    JustOneMore(x) -> never(x)
  }
}

// CONVERSIONS -----------------------------------------------------------------

/// 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.
///
pub fn void(_: a) -> Nil {
  Nil
}

// UTILS -----------------------------------------------------------------------

/// 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:
///
/// ```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`?
///
/// ```gleam
/// 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 fix(f) {
  fn(x) { f(fix(f), x) }
}

/// A version of the [`fix`](#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 fix2(f) {
  fn(x, y) { f(fix2(f), x, y) }
}

/// A version of the [`fix`](#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 fix3(f) {
  fn(x, y, z) { f(fix3(f), x, y, z) }
}

/// 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.
///
/// ```gleam
/// 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 then(first: fn(a) -> b, do second: fn(b) -> c) -> fn(a) -> c {
  fn(a) { first(a) |> second }
}