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