Current section
Files
Jump to
Current section
Files
src/funtil.erl
-module(funtil).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/funtil.gleam").
-export([never/1, void/1, fix/1, fix2/1, fix3/1, then/2]).
-export_type([never/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque never() :: {just_one_more, never()}.
-file("src/funtil.gleam", 53).
?DOC(
" If you've got a `Never` somewhere in a type it can be a bit of a problem if\n"
" you need something else. Just like [`Never`](#Never) is a type that can never\n"
" be constructed, `never` is a function that can never be called.\n"
"\n"
" To take our `Result(Int, Never)` example from above, what if we want to\n"
" pass that value into a function that expects a `Result(Int, String)`? As it\n"
" stands, the types don't match up, but because we know that we can never have\n"
" an error, we can use `never` to pretend to convert it into a `String`:\n"
"\n"
" ```gleam\n"
" import funtil.{Never, never}\n"
" import gleam/io\n"
" import gleam/result\n"
"\n"
" fn log_error(result: Result(a, String)) -> Result(a, String) {\n"
" case result {\n"
" Ok(a) -> Nil\n"
" Error(message) -> io.println(message)\n"
" }\n"
"\n"
" result\n"
" }\n"
"\n"
" fn example() {\n"
" let val: Result(Int, Never) = Ok(42)\n"
"\n"
" val\n"
" |> result.map_error(never)\n"
" |> log_error\n"
" }\n"
" ```\n"
).
-spec never(never()) -> any().
never(Val) ->
case Val of
{just_one_more, X} ->
never(X)
end.
-file("src/funtil.gleam", 65).
?DOC(
" Take any value and replace it with `Nil`. This can be a nicer way of using\n"
" value-producing functions in places where you only care about their side\n"
" effects.\n"
).
-spec void(any()) -> nil.
void(_) ->
nil.
-file("src/funtil.gleam", 116).
?DOC(
" Gleam's type system does not support recursive `let`-bound functions, even\n"
" though they are theoretically possible. The `fix` combinator is a sneaky way\n"
" around this limitation by making the recursive function a parameter of\n"
" itself.\n"
"\n"
" Sound a bit too magical? Let's first take a look at what happens if we try\n"
" to write a recursive `let`-bound function in Gleam:\n"
"\n"
" ```gleam\n"
" pub fn example() {\n"
" let factorial = fn(x) {\n"
" case x {\n"
" 0 -> 1\n"
" x -> x * factorial(x - 1)\n"
" // ^^^^^^^^^ The name `factorial` is not in scope here.\n"
" }\n"
" }\n"
"\n"
" assert fact(5) == 120\n"
" }\n"
" ```\n"
"\n"
" We get a compile error because the name `factorial` is not in scope inside\n"
" the function body. What does it look like if we try to use `fix`?\n"
"\n"
" ```gleam\n"
" import funtil\n"
"\n"
" pub fn example() {\n"
" let factorial =\n"
" funtil.fix(fn(factorial, x) {\n"
" case x {\n"
" 0 -> 1\n"
" x -> x * factorial(x - 1)\n"
" }\n"
" })\n"
"\n"
" assert fact(5) == 120\n"
" }\n"
" ```\n"
"\n"
" 🚨 Gleam is designed with this limitation to encourage you to pull things out\n"
" of `let` bindings when they get too complex. If you find yourself reaching for\n"
" `fix`, consider if there's a clearer way to solve your problem.\n"
).
-spec fix(fun((fun((DUG) -> DUJ), DUG) -> DUJ)) -> fun((DUG) -> DUJ).
fix(F) ->
fun(X) -> F(fix(F), X) end.
-file("src/funtil.gleam", 126).
?DOC(
" A version of the [`fix`](#fix) util for functions that take two arguments.\n"
"\n"
" 🚨 Gleam is designed with this limitation to encourage you to pull things out\n"
" of `let` bindings when they get too complex. If you find yourself reaching for\n"
" `fix2`, consider if there's a clearer way to solve your problem.\n"
).
-spec fix2(fun((fun((DUK, DUL) -> DUP), DUK, DUL) -> DUP)) -> fun((DUK, DUL) -> DUP).
fix2(F) ->
fun(X, Y) -> F(fix2(F), X, Y) end.
-file("src/funtil.gleam", 136).
?DOC(
" A version of the [`fix`](#fix) util for functions that take three arguments.\n"
"\n"
" 🚨 Gleam is designed with this limitation to encourage you to pull things out\n"
" of `let` bindings when they get too complex. If you find yourself reaching for\n"
" `fix3`, consider if there's a clearer way to solve your problem.\n"
).
-spec fix3(fun((fun((DUQ, DUR, DUS) -> DUX), DUQ, DUR, DUS) -> DUX)) -> fun((DUQ, DUR, DUS) -> DUX).
fix3(F) ->
fun(X, Y, Z) -> F(fix3(F), X, Y, Z) end.
-file("src/funtil.gleam", 166).
?DOC(
" Compose two functions together, where the output of the first function is\n"
" passed as the input to the second function. This is known as _composition_\n"
" and can be a way to write code in a \"point-free\" style where you don't\n"
" explicitly mention function arguments.\n"
"\n"
" ```gleam\n"
" import funtil\n"
" import gleam/list\n"
" import gleam/string\n"
"\n"
" fn example() {\n"
" let shouty_names =\n"
" list.map(\n"
" [\"yoshie\", \"danielle\", \"marniek\"],\n"
" string.uppercase |> funtil.then(string.append(_, \"!\"))\n"
" )\n"
"\n"
" assert shouty_names == [\"YOSHIE!\", \"DANIELLE!\", \"MARNIEK!\"]\n"
" }\n"
" ```\n"
"\n"
" 🚨 Gleam intentionally doesn't have an operator for function composition. In\n"
" other languages this is often represented as `.` or `>>`. Point-free programming\n"
" can be a useful tool but it can also make code harder to understand, hold with\n"
" care!\n"
).
-spec then(fun((DUC) -> DUD), fun((DUD) -> DUE)) -> fun((DUC) -> DUE).
then(First, Second) ->
fun(A) -> _pipe = First(A),
Second(_pipe) end.