An Elixir implementation of generic recursion schemes; functions for building and consuming recursively defined data structures.
See Functional Programming with Bananas Lenses Envelopes and Barbed Wire for the seminal paper describing recursion schemes.
Up-to-date docs can be found via hexdocs.pm/recursion_schemes.
Note: This library is in development and the API is subject to change.
cata/3
folds a structure (it is functionally equivalent to foldr
for lists):
[3, 5, 2, 9]
|> RecursionSchemes.cata(
0,
fn (h, acc) -> h + acc end)
# 19
# Calculate the factorial of a number:
RecursionSchemes.cata(
5,
1,
fn (x, acc) -> x * acc end
)
# 120
ana/3
unfolds a seed value using a function to generate new values:
# Generate a list of the squares of 1 - 10.
RecursionSchemes.ana(
{1, []},
fn x -> x > 10 end,
fn x -> {x * x, x + 1} end
)
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
hylo/5
unfolds a data structure from a seed and then folds the result:
# Generate a list of the squares from 1 - 10, then sum the result.
RecursionSchemes.hylo(
{1, []},
fn x -> x > 10 end,
fn x -> {x * x, x + 1} end
0,
fn (n, acc) -> n + acc end
)
# 385
para/3
folds a structure; it differs from cata
in that the folding function receives as arguments the current value, the remaining values in the structure, and the accumulator:
# Generate a list of the suffixes of a list:
RecursionSchemes.para(
[1, 2, 3, 4, 5],
[],
fn (_x, xs, acc) -> [xs | acc] end
)
# [[2, 3, 4, 5], [3, 4, 5], [4, 5], [5], []]
apo/2
unfolds a structure from a seed as long as the unfolding function returns {:ok, value}
; unfolding stops when {:halt, value}
is returned:
# Zip a pair of lists:
{{[1,2,3,4], ["a", "b", "c"]}, []}
|> RecursionSchemes.apo(
fn {[a | as], [b | bs]} ->
if as == [] or bs == [] do
{{:halt, {a, b}}, nil}
else
{{:ok, {a, b}}, {as, bs}}
end
end)
# [{1, "a"}, {2, "b"}, {3, "c"}]
- catamorphism
cata/3
cata/2
- paramorphism
para/3
para/2
- anamorphism
ana/3
ana/2
- apomorphism
apo/2
apo/1
- hylomorphism
hylo/5
hylo/2
Add `recursion_schemes` to your list of dependencies in `mix.exs`:
def deps do
[{:recursion_schemes, "~> 0.2.0"}]
end