funtil
Types
This type is used to represent a value that can never happen. What does that mean exactly?
- A
Boolis a type that has two values:TrueandFalse. Nilis a type that has one value:Nil.Neveris 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
}