Recursion and Constructing Combinators
An Intermediate+n Tutorial by Jonathan Carlos
Baca
1. What Recusion Is and Is Not
This is a tutorial that will not teach you how to use a certain library, popular programming technique, or even make you a faster
coder. However, I maintain that this tutorial will make you a better coder; code is logic knit into forms by human fingertips
dancing on keyboards, and although you may just want to dab, why not learn ballet as well?
For this tutorial to help you out, you must know what recursion is. This is actually pretty simple. Suppose we have a function
called ‘foo’, defined as follows (I’m using Javascript, so haters; bear along. For some of you, I’ve also written the tutorial in
Lisp, so if you’re that type of purist, you’ll be pretty happy. For beginners: the first examples are always in Javascript, and the
second ones are in Lisp, but you’ll get a feel for which is which almost immediately):
Javascript
1
2
3
4
5
6
function foo(n) {
if(n == 0) {
return n;
}
return 1 + foo(n - 1);
}
JavaScript
Lisp
1
2
3
4
(defun foo (n)
(if (zerop n)
n
(1+ (foo (1- n)))))
What output will foo(3) produce? Well let’s walk through it. if n:=3, then n == 0 is false, so it will return 1 + foo(2). So what’s
foo(2)? In the same manner, it’s 1 + foo(1). What’s foo(1)? Again, 1 + foo(0). Now what’s foo(0)? Since here n:=0, we return n
(which is 0), so foo(0) = 0. So 1 + foo(0) = foo(1) = 1, 1 + foo(1) = foo(2) = 2, and 1 + foo(3) = foo(3) = 3. If you haven’t figured it
out yet, it just takes a positive integer and regurgitates it.
The question I’m posing seems like a simple one: can recursion exist without functions being able to refer back to themselves?
This may seem like an obvious ‘no’ to some of you, ‘yes’ to others, and to those of you in the back (who should give the other
students a chance) shouting about the Y-combinator: shut up a minute. Let me make my meaning more clear: suppose we’re
given a computer language (I’ve chosen javascript to write this tutorial in, although any functionalish language will do) that has
names for variables and functions and has a mechanism for passing those functions and variables into other functions. The
question is this: ‘Must a function refer to itself by the name it was given upon definition to calculate recursive functions in a
more-or-less direct way?’ It’s a nebulous question, to be sure.
2. Ouroboros (the snake eating its own tail)
Suppose we want to recurse without naming the recursor. We can start by writing a simple recursive function. I’ve chosen
multiplication. The identity we’ll be using is 0 * b = 0, but if a != 0, then a * b = (1 + (a - 1)) * b = b + (a - 1) * b (or, in English, zero
times anything is zero, and thing times second thing is equal to second thing plus first thing minus one. So 3 * 5 = 5 + (2 * 10),
and so on:
Javascript
1
2
3
4
5
6
function multiply(a, b) {
if(a == 0) {
return 0;
}
return b + multiply(a-1, b);
}
JavaScript
Lisp
1
2
3
4
(defun multiply (a b)
(if (zerop a)
0
(+ b (multiply (1- a) b))))
We could write this more succinctly (and without structures) as:
Javascript
1
2
3
function multiply(a, b) {
return a? b + multiply(a-1, b) : 0;
}
JavaScript
Now suppose we have a function called ‘doMultiply’, which does everything that multiply does, but has to be explicitly given
itself as an argument in order to recurse using itself. We could do something like this:
Javascript
1
2
3
4
5
6
function
return
}
function
return
}
doMultiply(dm, a, b) {
a? b + dm(dm, a-1, b) : 0;
JavaScript
multiply(a, b) {
doMultiply(doMultiply, a, b);
Lisp
1
2
3
4
5
6
(defun do-multiply (dm a b)
(if (zerop a)
0
(+ b (funcall dm a b))))
(defun multiply (a b)
(do-multiply #'do-multiply a b))
And to tidy things up, I’ve introduced a ‘recursor’ functino that just supplies a function with itself and its ‘base’ arguments:
Javascript
1
2
3
4
5
6
7
8
9
function
return
}
function
return
}
function
return
}
doMultiply(dm, a, b) {
a ? b + dm(dm, a-1, b) : 0;
JavaScript
recursor(fxn, a, b) {
fxn(fxn, a, b);
multiply(a, b) {
recursor(doMultiply, a, b);
Lisp
1
2
3
4
5
6
7
8
9
10
(defun do-multiply (dm a b)
(if (zerop a)
0
(+ b (funcall dm dm (1- a) b))))
(defun recursor (f a b)
(funcall f f a b))
(defun multiply (a b)
(recursor #'do-multiply a b))
In javascript, there’s a tidy little notation that I like to call parenfuncs or (..)=>….-style expressions. The way these work is that
you have a list of arguments followed by a block or expression. For the sake of beauty, the only operator we’re going to allow is
the ?: triadic operator (where a?b:c yields b if a evaluates to true/not-false, and b otherwise), and we’re not going to allow any
brackets. Our function is simple enough not to need a lot of reconfiguring, and the functions above could be defined as such:
Javascript
1
2
3
var doMultiply = (dm, a, b) => a ? b + dm(dm, a-1, b) : 0;
var recursor = (fxn, a, b) => fxn(fxn, a, b);
var multiply = (a, b) => recursor(doMultiply, a, b);
JavaScript
Now the beauty of the parenfuncs (especially with the guidelines I set forth) is that you can replace any occurrence of a named
parenfunc with the goop inside it. For example, if we’re given:
Javascript
1
2
var bar = (n) => n * n;
var foo = (a, b) => bar(a - 1) - b;
JavaScript
We can replace the second bar with what’s left of the =>, substituting ‘a - 1’ for ‘n’:
Javascript
1
var foo = (a, b) => (a - 1) * (a - 1) - b;
Verbose? Yes. Tedious? Yes. But please bear with me. Let’s ‘fold’ the recursor function into the multiply function:
JavaScript
Javascript
1
2
var doMultiply = (dm, a, b) => a ? b + dm(dm, a-1, b) : 0;
var multiply = (a, b) => ((fxn, a, b) => fxn(fxn, a, b))(doMultiply, a, b);
JavaScript
Lisp
1
2
3
4
5
6
7
(defun do-multiply (dm a b)
(if (zerop a)
0
(+ b (funcall dm dm (1- a) b))))
(defun multiply (a b)
(funcall #'do-multiply #'do-multiply a b))
And finally, let’s ‘fold’ the ‘doMultiply’ function into the multiply function.
Javascript
1
2
var multiply = (a, b) => ((fxn, a, b) => fxn(fxn, a, b))((dm, a, b) =>
a ? b + multiply(dm, a-1, b) : 0, a, b);
JavaScript
Lisp
1
2
3
4
5
6
7
(defun multiply (a b)
((lambda (fxn a b) (funcall fxn fxn a b))
#'(lambda (dm a b)
(if (zerop a)
0
(+ b (funcall dm dm (1- a) b))))
a b))
3. Down the Rabbit Hole
And just to be a little crazy, we’re not even gonna add automatically. We’ll go through the process somewhat quicker here. We
start with a recursive function:
Javascript
1
2
3
var add(c, d) {
return c ? 1 + add(c - 1, d) : d;
}
Lisp
1
2
3
4
(defun add (c d)
(if (zerop c)
d
(1+ (add (1- c) d))))
JavaScript
Redefine it to use the ‘recursor’ function (seen above):
Javascript
1
2
3
var doAdd = (da, c, d) => c ? 1 + da(da, c-1, d) : d;
var add = (c, d) => recursor(doAdd, c, d);
var recursor = (fxn, a, b) => fxn(fxn, a, b);
JavaScript
Lisp
1
2
3
4
5
6
7
8
9
10
(defun do-add (da c d)
(if (zerop c)
d
(1+ (funcall da da (1- c) d))))
(defun recursor (fxn a b)
(funcall fxn fxn a b))
(defun add (c d)
(recursor #'do-add c d))
So let’s look back at our ‘multiply’ function, and put in the ‘add’ function without folding it in:
Javascript
1
2
3
var multiply = (a, b) => ((fxn, a, b) =>
fxn(fxn, a, b))((dm, a, b) =>
a ? add(b, multiply(dm, a-1, b)) : 0, a, b);
JavaScript
Lisp
1
2
3
4
5
6
7
8
9
(defun multiply (a)
(lambda (b)
((lambda (f)
(funcall (funcall (funcall f f) a) b)) #'(lambda (dm)
(lambda (x)
(lambda (y)
(if (zerop x)
0
(add y (funcall (funcall (funcall dm dm) (1- x)) y))))))))
And then fold the add function together in a similar fashion.
Javascript
1
2
Lisp
var add = (c, d) => ((fxn, c, d) => fxn(fxn, c, d))
((da, c, d) => c ? 1 + da(da, c-1, d) : d, c, d);
JavaScript
1
2
3
4
5
6
7
8
(defun add (c d)
((lambda (fxn c d)
(funcall fxn fxn c d))
#'(lambda (da c d)
(if (zerop c)
d
(1+ (funcall da da (1- c) d))))
c d))
When we fold in the ‘add’ function; when c := b, and d := dm(dm, a-1, b), the add(b, dm(dm, a-1, b)) becomes:
Javascript
1
2
3
((fxn, c, d) => fxn(fxn, c, d))((da, c, d) => c ?
1 + da(da, c-1, d)
: d, b, dm(dm, a-1, b))
JavaScript
Lisp
1
2
3
4
5
6
7
((lambda (fxn c d)
(funcall fxn fxn c d))
#'(lambda (da c d)
(if (zerop c)
d
(1+ (funcall da da (1- c) d))))
b (funcall dm dm (1- a) b))
And putting it together, we get:
Javascript
1
2
3
4
Lisp
var multiply = (a, b) => ((fxn, a, b) =>
fxn(fxn, a, b))((dm, a, b) => a ?
((fxn, c, d) => fxn(fxn, c, d))((da, c, d) => c ? d
: 1 + da(da, c-1, d), b, dm(dm, a-1, b)) : 0, a, b);
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defun multiply (a b)
((lambda (fxn a b)
(funcall fxn fxn a b))
#'(lambda (dm a b)
(if (zerop a)
0
((lambda (fxn c d)
(funcall fxn fxn c d))
#'(lambda (da c d)
(if (zerop c)
d
(1+ (funcall da da (1- c) d))))
b (funcall dm dm (1- a) b))))
a b))
Or, putting it more one-letter-per-concept-y (i.e., cryptically):
Javascript
1
2
3
4
5
6
7
8
9
10
11
var multiply =
(a, b) =>
((f, a, b) =>
f(f, a, b))(
(f, a, b) =>
a ? ((g, c, d) =>
g(g, c, d))(
(g, c, d) =>
c == 0 ? d : 1 + g(g, c-1, d), b, f(f, a-1, b)) : 0,
a,
b)
JavaScript
Lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defun multiply (a b)
((lambda (f a b)
(funcall f f a b))
#'(lambda (f a b)
(if (zerop a)
0
((lambda (g c d)
(funcall g g c d))
#'(lambda (g c d)
(if (zerop c)
d
(1+ (funcall g g (1- c) d))))
b (funcall f f (1- a) b))))
a b))
That’s a lot of layers! And now we do something interesting. Suppose we only allow monads (functions with one input, not an
ordered set of inputs). We can do this in the following way. Suppose we start with this:
Javascript
1
2
3
function foo(a, b, c) {
return 3 * a - (b - 1) * (c - 2);
}
JavaScript
Lisp
1
2
(defun foo (a b c)
(- (* 3 a) (* (1- b) (- c 2))))
We could have this return a function that takes only a, and returns a function which takes in (b, c) and outputs the same value.
This may seem like a lateral step, but notice that we’re getting rid of commas.
Javascript
1
2
3
4
5
function foo(a) {
return function(b, c) {
return 3 * a - (b - 1) * (c - 2);
}
}
JavaScript
Lisp
1
2
3
(defun foo (a)
(lambda (b c)
(- (* 3 a) (* (1- b) (- c 2)))))
One step further, we get:
Javascript
1
2
3
4
5
6
7
function foo(a) {
return function(b) {
return function(c) {
return 3 * a - (b - 1) * (c - 2);
}
}
}
Lisp
1
2
3
4
(defun foo (a)
(lambda (b)
(lambda (c)
(- (* 3 a) (* (1- b) (- c 2))))))
“Of course, a call with the original ‘foo’ would be something like:
Javascript
JavaScript
1
foo(3,4,6)
JavaScript
Lisp
1
(foo 3 4 6)
And get -3. With the latest foo, we have to oo:
Javascript
1
foo(3)(4)(6)
JavaScript
Lisp
1
(funcall (funcall (foo 3) 4) 6)
And still we get -3. Doing this with (..)=>….-style expressions, we get the following progression:
Javascript
1
2
3
var foo = (a, b, c) => 3 * a - (b - 1) * (c - 2);
var foo = (a) => (b, c) => 3 * a - (b - 1) * (c - 2);
var foo = (a) => (b) => (c) => 3 * a - (b - 1) * (c - 2);
JavaScript
And it works just like the final foo! Try it yourself. Using these principles (and, admittedly, a little bit of trial and error (I know
what I mean; why should the computer have to?), I derived the following expression for ‘multiply’:
Javascript
1
2
3
4
5
6
7
8
9
Lisp
var multiply =
(a)=>(b)=>
((f)=>(a)=>(b)=>
f(f)(a)(b))(
(f) => (a) => (b) =>
a ? ((g) => (c) => (d) =>
g(g)(c)(d))(
(g) => (c) => (d) =>
c == 0 ? d : 1 + g(g)(c-1)(d))(b)(f(f)(a-1)(b)) : 0)(a)(b)
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defun multiply (a)
(lambda (b)
((lambda (f)
(funcall (funcall (funcall f f) a) b)) #'(lambda (dm)
(lambda (x)
(lambda (y)
(if (zerop x)
0
(funcall ((lambda (c) (lambda (d)
(funcall (funcall ((lambda (fxn) (lambda (a) (lambda (b)
(funcall (funcall (funcall fxn fxn) a) b)))) #'(lambda (da)
(lambda (c)
(lambda (d)
(if (zerop c)
d
(1+ (funcall (funcall (funcall da da) (1- c)) d))))))) c) d))) y)
(funcall (funcall (funcall dm dm) (1- x)) y)))))))))
"Adding words back in, you can see that it’s still pretty cryptic.
Javascript
1
2
3
4
5
6
7
8
9
10
11
Lisp
var multiply =
(multiplier)=>(multiplicand)=>
((doMultiply)=>(multiplier)=>(multiplicand)=>
doMultiply(doMultiply)(multiplier)(multiplicand))(
(doMultiply) => (multiplier) => (multiplicand) =>
multiplier ? ((doAdd) => (adder) => (addend) =>
doAdd(doAdd)(adder)(addend))(
(doAdd) => (adder) => (addend) =>
adder == 0 ? addend : 1 + doAdd(doAdd)(adder-1)(addend))
(multiplicand)(doMultiply(doMultiply)(multiplier-1)(multiplicand))
: 0)(multiplier)(multiplicand)
JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(defun multiply (multiplier)
(lambda (multiplicand)
((lambda (multiplier-recursor)
(funcall (funcall
(funcall multiplier-recursor multiplier-recursor)
multiplier)
multiplicand))
#'(lambda (do-multiply)
(lambda (multiplier)
(lambda (multiplicand)
(if (zerop multiplier)
0
(funcall ((lambda (adder) (lambda (addend)
(funcall (funcall
((lambda (adder-recursor)
(lambda (multiplier)
(lambda (multiplicand)
(funcall (funcall
(funcall adder-recursor adder-recursor)
multiplier)
multiplicand))))
#'(lambda (do-add)
(lambda (adder)
(lambda (addend)
(if (zerop adder)
addend
(1+ (funcall (funcall
(funcall do-add do-add)
(1- adder))
addend))))))) adder) addend)))
multiplicand)
(funcall (funcall
(funcall do-multiply do-multiply)
(1- multiplier))
multiplicand)))))))))
Try it yourself. multiply(2)(3) yields 6. This is a slow algorithm; it only counts one at a time. So while we’re writing toy programs,
why not go full hog with the crypticality? We don’t even need names (except fluid names to refer to variables within the current
context – no ‘external’ variables):
Javascript
1
Lisp
JavaScript
((c)=>(a)=>(b)=>c(c)(a)(b))((c)=>(a)=>(b)=>a?((d)=>(e)=>(f)=>d(d)(e)(f))((d)=>(e)=>(f)=>e==0?f:1+d(d)(e-1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(funcall ((lambda (multiplier)
(lambda (multiplicand)
((lambda (multiplier-recursor)
(funcall (funcall
(funcall multiplier-recursor multiplier-recursor)
multiplier)
multiplicand)) #'(lambda (do-multiply)
(lambda (multiplier)
(lambda (multiplicand)
(if (zerop multiplier)
0
(funcall ((lambda (adder) (lambda (addend)
(funcall (funcall
((lambda (adder-recursor)
(lambda (multiplier)
(lambda (multiplicand)
(funcall (funcall
(funcall adder-recursor adder-recursor)
multiplier)
multiplicand)))) #'(lambda (do-add)
(lambda (adder)
(lambda (addend)
(if (zerop adder)
addend
(1+
(funcall (funcall (funcall do-add do-add)
(1- adder))
addend)))))))
adder)
addend)))
multiplicand)
(funcall (funcall
(funcall do-multiply do-multiply)
(1- multiplier))
multiplicand)))))))))
2) 3)
4. Behind the Scenes
If you try this with large numbers like 35,543,512 and 578,512, you’ll get either a stack error after a second or two, or if you
have an insane amount of RAM and a great javascript interpreter, you might just freeze your computer for a day or so on a fast
processor. This is because, at a base level, what you are doing when you multiply 2 and 3 is this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mult(2, 3)
add(3, mult(1, 3))
add(3, add(3, mult(0, 3)))
add(3, add(3, 0))
add(3, 1+ add(2, 0))
add(3, 1+ (1+ add(1, 0)))
add(3, 1+ (1+ (1+ add(0, 0))))
add(3, 1+ (1+ (1+ 0)))
add(3, 1+ (1+ 1))
add(3, 1+ 2)
add(3, 3)
1+ add(2, 3)
1+ (1+ add(1, 3))
1+ (1+ (1+ add(0, 3)))
1+ (1+ (1+ 3))
1+ (1+ 4)
1+ 5
6
So to get six, our final answer, we basically had to count one-by-one.
You may be wondering why we’re allowing the 1+x operation (such as (1 + 3) -> 4) to take place without deconstruction. The
reason why is because there has to be some atomic operation that changes numbers. In Peano arithmentic (where this tutorial
took inspiration from), this is known as the successor function. That is to say, there is a ‘0’ (by law), and a ’S(uccessor)‘ function
(by law). The 'meaning’ of the successor function of x (S(x)) is, intuitively, ‘one plus x’, but the axioms never state this (because
they are an attempt at symbolic purity). So ‘1’ is just shorthand for S(0), ‘2’ is just S(1) = S(S(0)), and something like ‘11’ is just
our place-value shorthand for S(S(S(S(S(S(S(S(S(S(S(0))))))))))) (whew).
5. Recursion: Every Change Unquestionably Recurrant; Self Instantiation Over Nothing
So what have we learned here? We’ve learned that recursion is possible without referring (explicitly) back to the original
function, but we’ve also learned a valuable lesson about assumptions. Even with something that beginning students find
challenging (such as recursion), there is a wealth of knowledge to be found by asking even the most basic of questions.”