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.
///       }
///     }
/// 
///   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`?
/// 
/// ```gleam
/// 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 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) }
}