Recursion#

Just as normal lambda calculus, bruijn does not support typical recursion.

If you want to recursively call a function (or imitate for/while loops), you need to use fixed-point combinators like y from std/Combinator.

Fixed-point combinators have the fascinating property of inducing recursive behaviour in programming languages without support for recursion.

Say we want a function g to be able to call itself. With the y combinator the following equivalence is obtained:

  (y g)
⤳ [[1 (0 0)] [1 (0 0)]] g
⤳ [g (0 0)] [g (0 0)]
⤳ g ([g (0 0)] [g (0 0)])
≡ g (y g)

With this equivalence, g is able to call itself since its outer argument is the initial function again.

Example for using y to find the factorial of 3:

# here, `1` is the induced outer argument `(y g)`
# `0` is the accumulator (the argument of `factorial`)
g [[=?0 (+1) (0 ⋅ (1 --0))]]

factorial y g ⧗ Number → Number

:test ((factorial (+3)) =? (+6)) (true)

In the wild it might look like this:

# 3 abstractions => two arguments
# 2 is recursive call
# 1 is accumulator (+0)
# 0 is argument (list)
length z [[[rec]]] (+0) ⧗ (List a) → Number
    rec 0 [[[case-inc]]] case-end
        case-inc 5 ++4 1
        case-end 1

Read list data structure for more information. Also read coding style for other style suggestions.

Mutual recurrence relations#

For solving mutual recurrence relations, you can use the variadic fixed-point combinator y* from std/List. This combinator produces all the fixed points of given functions as an iterable list.

Example even?/odd? implementation using y*:

# the odd? recursive call will be the second argument (1)
g [[[=?0 true (1 --0)]]]

# the even? recursive call will be the first argument (2)
h [[[=?0 false (2 --0)]]]

even? head (y* (g : {}h)) ⧗ Number → Boolean

odd? tail (y* (g : {}h)) ⧗ Number → Boolean

Read more about this in the blog post Variadic fixed-point combinators.