# The Y combinator - understanding recursion without recursion

October 30, 2019

## Introduction

Recursion is central to functional programming, as a clearer alternative to loops as other control structures typical of imperative languages. Functional programming encourages programmers to study recursion in greater depths. I first encountered the Y combinator in the mind-bending penultimate chapter of the wonderful *The Little Schemer*, which explores recursion in great depth. In an effort to unbend my own mind on the subject, I decided to derive it for myself, so I could see how it worked, and gain an extra tool in dealing with recursion and closures.

Do you now know why Y works? Read this chapter just one more time and you will –

The Little Schemer

The Y combinator was discovered by Haskell Curry in the 1940s. It allows recursion to be captured without functions needing to reference themselves by name. It provides some insight into the nature of recursion in the lambda calculus (where nothing has a name), and also demonstrates the power of closures.

We will conduct our explorations mostly in scheme, because it's expressive, concise and elegant, but we also give examples in Haskell and JavaScript at the end. The Haskell version is ludicrously simple and clear (relying on lazy evaluation), while the JavaScript version mirrors the Scheme version.

### Further reading

*The Little Schemer* gives a great introduction to recursion, including a section on the Y combinator. The presentation here is a little different, since I wanted a more direct understanding of how the Y combinator worked.

## Recursive functions as fixed points of (higher-order) functions

The Y combinator allows the programmer to pass in a function which isn't explicitly recursive (doesn't reference itself by name), but describes a step in a recursive process with a continuation, and provides back a new function which recursively applies that step using itself as the continuation.

Let's start by making the term "step in a recursive process with a continuation" more concrete, and clarify how the Y combinator acts on these steps.

To give us something specific to think about, let's examine the factorial function. The classic recursive definition of factorial is expressed in Scheme as follows

```
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
```

This definition references itself. We can view it as an equation in terms of `factorial`

, however. In fact, we can define

```
(define (factorialize f)
(lambda (n)
(if (= n 0)
1
(* n (f (- n 1))))))
```

and observe that `(factorialize factorial)`

(`factorialize`

applied to the `factorial`

function) is itself `factorial`

. The formal way to say this is that `factorial`

is a fixed-point of `factorialize`

.

Its important to understand that `factorialize`

operates on all functions from numbers to numbers. In terms of function, we can look at `factorialize`

as doing a single step in the factorial function, and then, instead of recursing, handing off the remainder of the work to a continuation which is passed in as a parameter. This is what is meant by a "step in a recursive process with a continuation". `factorialize`

itself is not recursive - it hands the recursion over to some continuation which is passed in.

The Y combinator turns these recursive steps into full-blown recursive functions. Applied to `factorialize`

it finds a fixed point (you can prove by induction that such a fixed point is necessarily the `factorial`

function). This means we can define the factorial function by

```
(define factorial
(Y factorialize))
```

where here, `Y`

is the Y combinator. How does it do this? In effect it passes `factorialize`

in as the continuation to `factorialize`

, so that the same recursive step is applied over-and-over, until we reach the base case.

## Deriving the Y combinator

### Capturing our own value

The fixed point perspective is a useful starting point, because we want the Y combinator applied to `factorialize`

to be an expression `expr`

which satisfies

`expr = (factorialize expr)`

One possible starting point is to ask, how can an expression capture it's own value? That is, can we write an expression, which, inside itself, has a handle on its own value.

The trick to doing this is to observe that applying the anonymous function `(lambda (f) (f f))`

to a function allows a function to receive itself as an argument. If we feed this function another function, `(lambda (recur) ...)`

, and try to evaluate it

```
((lambda (f) (f f))
(lambda (recur)
...))
```

Then inside the inner lambda, `recur`

will be bound to the `(lambda (recur) ...)`

. But then `(recur recur)`

is just the inner lambda `(lambda (recur) ...)`

applied to itself, which is the value of the expression we're trying to evaluate (that might take a few reads!).

In other words, if we try to evaluate the following

```
((lambda (f) (f f))
(lambda (recur)
(recur recur)))
```

by applying the outer function, we get back to where we started. If you try to evaluate this, it will just loop forever! In Haskell we could write this as

`let x = x in x`

Since we are looking for a function `exp`

with value `(factorialize exp)`

, and we know `(recur recur)`

has value `exp`

in the above, we can try inserting a call to factorialize:

```
((lambda (f) (f f))
(lambda (recur)
(factorialize (recur recur))))
```

By the same reasoning, if this has value `v`

, then by applying the outer `lambda`

, we see it also has value `(factorialize v)`

. Great! We've found a fixed point for `factorialize`

, and hence this must be the `factorial`

function. In fact, if we parameterize over `factorialize`

then we have the (formal) Y combinator!

### Making it run

What happens when we try and evaluate this? Firing up a Scheme interpreter and plugging it in

```
((lambda (f) (f f))
(lambda (recur)
(factorialize (recur recur))))
;Aborting!: maximum recursion depth exceeded
```

Hmm. The issue here is that when trying to evaluate this procedure, `(recur recur)`

has to be fully evaluated before a call to `factorialize`

is made (scheme evaluates its arguments before calling functions). This means for our expression `exp`

to be evaluated, `exp`

(`(recur recur)`

) must first be evaluated - this leads to an infinite loop!

To fix this, we want to delay the evaluation of `(recur recur)`

until it is needed (in other words evaluate it lazily). We can do this with the aid of a lambda:

```
((lambda (f) (f f))
(lambda (recur)
(factorialize (lambda (x) ((recur recur) x)))))
```

Let's try it:

```
(((lambda (f) (f f))
(lambda (recur)
(factorialize (lambda (x) ((recur recur) x))))) 0)
;Value: 1
```

```
(((lambda (f) (f f))
(lambda (recur)
(factorialize (lambda (x) ((recur recur) x))))) 5)
;Value: 120
```

Looking good! Notice that none of this has anything to do with `factorialize`

. We can parameterise and abstract:

```
(define (Y F)
((lambda (f) (f f))
(lambda (recur)
(F (lambda (x) ((recur recur) x))))))
```

Hello Y combinator!

## Examples of the Y combinator in action

We started with the factorial function, so that ought to work as expected:

```
(define factorial
(Y
(lambda (recur)
(lambda (n)
(if (= n 0)
1
(* n (recur (- n 1))))))))
(factorial 5)
;Value: 120
```

Another easy example is defining the length of a list:

```
(define length
(Y
(lambda (recur)
(lambda (l)
(if (null? l)
0
(+ 1 (recur (cdr l))))))))
(length '(1 2 3))
;Value: 3
```

Multiple calls to recur also work just fine:

```
(define fibonacci
(Y
(lambda (recur)
(lambda (n)
(cond
((= n 0) 0)
((= n 1) 1)
(else (+ (recur (- n 1))
(recur (- n 2)))))))))
(map fibonacci (list 0 1 2 3 4 5 6 7 8))
;Value: (0 1 1 2 3 5 8 13 21)
```

We can also use the Y combinator's definition to write recursive lambdas inline. To give a contrived example using length of lists:

```
(map
((lambda (F)
((lambda (f) (f f))
(lambda (recur)
(F (lambda (x) ((recur recur) x))))))
(lambda (recur)
(lambda (l)
(if (null? l)
0
(+ 1 (recur (cdr l)))))))
'((1) (1 2) (1 2 3)))
;Value: (1 2 3)
```

## Intuitions about Y as a limit

Let's try and get a different intuition for how Y works. Let's lean on our `factorial`

and `factorialize`

example some more.

One intuitive way to get `factorial`

out of `factorialize`

, is to pass `factorialize`

something like `factorialize`

as it's argument. In fact, each of the following gets closer and closer to `factorial`

```
(factorialize factorialize) ; Evaluates correctly for 0
(factorialize (factorialize factorialize)) ; Evaluates correctly for 0, 1
(factorialize (factorialize (factorialize factorialize))) ; Evaluates for correctly 0, 1, 2
...
```

One can think of `factorial`

as being something like

`(factorialize (factorialize (factorialize ...)))`

Notice that to evaluate `factorial`

on a given number, we only need finitely many of these calls to `factorialize`

.

In fact, if we expand, for example, `((Y factorialize 3))`

we get

```
(factorialize
(factorialize
(factorialize
((factorialize _) 0))))
```

where the `_`

represents a `lambda`

which is never evaluated.

## In some other languages

The Y combinator is not restricted to Lisps. Let's give examples of the Y combinator in Haskell and JavaScript.

### The Y combinator in Haskell

Lazy evaluation means that in Haskell we don't need to work so hard, and we can just write down what i means to be a fixed point

```
y :: (a -> a) -> a
y f = let g = f g in g
factorial :: Integer -> Integer
factorial = y factorialize
where
factorialize _ 0 = 1
factorialize recur n = n * recur (n - 1)
```

This to me seems something close to magic, even though in many ways its simpler to think about than the Scheme version. `y`

is more often named `fix`

in the Haskell community, presumably because it is a definition by equation of a fixed-point. Haskell really is quite beautiful.

### The Y combinator in JavaScript

JavaScript is not so beautiful. Lambdas and functions might be the only sensible parts of JavaScript, but that's an extraordinarily powerful part all the same. Another grace is that you can even try this snippet in the developer console of your browser.

```
const y = (f) => ((g) => g(g))(
(recur) => f((x) => recur(recur)(x))
);
const factorialize = (recur) => (n) => n == 0 ? 1 : n * recur (n - 1);
const factorial = y(factorialize);
```

## Conclusions

Lambdas really are way more powerful than you would at first think! I found understanding how to derive the Y combinator gives me a new way to think. There really are good reasons why functional programming reveres the lambda so much - they are the Jedi weapon par excellence.

I can imagine many folks would balk at the code for the y combinator - it's hard to see what it does at a glance. It's nonetheless wonderfully abstract, and can be understood by its properties. Nonetheless, explicit recursion is probably clearer.