Renato Cordeiro Ferreira
Scientific Programmer @ JADS | PhD Candidate @ USP | Co-founder & Coordinator @CodeLab
Recursive Structures
MAC0316/5754
Concepts of Programming Languages
Or: To make recursion, is it necessary to know how to make recursion?
Renato Cordeiro Ferreira
This work is licensed under
Creative Commons Attribution-ShareAlike 4.0 International License
Waterfall,
M. C. Escher, 1961
Recursion in programming
Recursion with state
The command letrec, which we implemented in our last assignment, uses state to create recursion. The code:
(let ([fac '()])
(begin (set! fac
(lambda (n) (if (= n 0) 1 (* n (fac (- n 1))))))
(fac 5)))
(letrec ([fac (lambda (n)
(if (= n 0) 1 (* n (fac (- n 1)))))])
(fac 5))
Is equivalent to binding the name of the function with a dummy value (let), then rebinding it (set!) with the closure of the function body, then finally calling the function (begin):
Recursion with state
In our interpreter, this code can be implemented with boxes, which are a special value that can be manipulated with the operations box, unbox and set-box!
Instead of binding ids with values in an environment:
id
value
bind
The interpreter binds ids with locations in an environment, which then make cells with values in a store:
id
value
location
environment
bind
cell
environment
store
Recursion with state
Recursion with state has a problem: what does it happen if the value of a box / variable is used before its value is set? We have a leak of the temporary (uninitialized) value.
Some possible solutions for this problem include:
Is it possible to make recursion without state?
Recursion without state
Haskell Brooks Curry
(September 12, 1900 – September 1, 1982)
American mathematician and logician who worked with combinatorics logic.
He gives name to the currying technique and 3 (functional) programming languages:
Recursion without state
Curry was the first to describe a technique that could be used to generate recursion without state, the Y combinator, which can be defined as:
(define Y (
λ (p) (
(λ (f) (p (f f)))
(λ (f) (p (f f)))
)
))
(define Fact
(Y (λ(fact) (λ(n) (if (zero? n) 1 (* n (fact (- n 1))))))))
And can be applied as:
Recursion without state
The Y combinator is a fixed-point operator of lambda calculus. It is mathematically described as:
If we expand the Y combinator with a function \( g \) (an operation known as β-reduction in lambda calculus), then we have:
Recursion without state
We can try to deduce the Y combinator from the implementation of the factorial:
(define Fact
(λ (n)
(if (zero? n) 1
(* n (??? (- n 1)))
))
)
We would like to call Fact in the gap, but it is not possible since it is not defined in that point.
What if we replaced the gap by an auxiliary function?
Recursion without state
The problem is that mk-fact receives a function as a parameter, so we cannot call it this way.
(define mk-fact
(λ (f)
(λ (n)
(if (zero? n) 1
(* n (f (- n 1)))
)))
)
(define Fact
(λ (n)
(if (zero? n) 1
(* n (mk-fact (- n 1)))
))
)
Recursion without state
We need to pass something else to mk-fact so that, after applied, we get the body. What if we use mk-fact itself?
(define mk-fact
(λ (f)
(λ (n)
(if (zero? n) 1
(* n ((f ???) (- n 1)))
)))
)
(define Fact
(λ (n)
(if (zero? n) 1
(* n ((mk-fact ???) (- n 1)))
))
)
Recursion without state
It works! The argument is duplicated on each call, so that a new body of the function is created. But the code is duplicated.
(define mk-fact
(λ (f)
(λ (n)
(if (zero? n) 1
(* n ((f f) (- n 1)))
)))
)
(define Fact
(λ (n)
(if (zero? n) 1
(* n ((mk-fact mk-fact) (- n 1)))
))
)
Recursion without state
Fact can be deduced directly from mk-fact. It does not worth to create an auxiliary function. We can reduce it further.
(define mk-fact
(λ (f)
(λ (n)
(if (zero? n) 1
(* n ((f f) (- n 1)))
)))
)
(define Fact
(mk-fact mk-fact))
Recursion without state
The logic that does the duplication is mixed with the logic of the factorial function. Can we extract the duplication logic?
(define Fact
(
(λ (mk-fact) (mk-fact mk-fact))
(λ (f)
(λ (n)
(if (zero? n) 1
(* n ((f f) (- n 1)))
)))
)
)
Recursion without state
Now the body of the factorial is isolated from the rest. We can define a general function that makes the recursion.
(define Fact
(
(λ (mk-fact) (mk-fact mk-fact))
(λ (f)
(
(λ (fact)
(λ (n)
(if (zero? n) 1
(* n (fact (- n 1)))
)))
(f f)
)
)
)
)
Recursion without state
Yay!. We finalmy have the Y Combinator. This form is slightly different from our first slide, but can be β-reduced to it.
(define Y
(λ (p) (
(λ (f) (f f))
(λ (f) (p (f f)))
))
)
(define Fact
(Y (λ (fact)
(λ (n)
(if (zero? n) 1
(* n (fact (- n 1)))
))))
)
Recursion without state
There is only one problem: (f f) is calculated before the body, so there is infinite recursion. We need to make it lazy.
(define Y
(λ (p) (
(λ (f) (p (f f)))
(λ (f) (p (f f)))
))
)
(define Fact
(Y (λ (fact)
(λ (n)
(if (zero? n) 1
(* n (fact (- n 1)))
))))
)
Recursion without state
Adding another lambda solves this problem. The new function is called a Turing combinator.
(define Turing
(λ (p) (
(λ (f) (p (λ (a) ((f f) a))))
(λ (f) (p (λ (a) ((f f) a))))
))
)
(define Fact
(Turing (λ (fact)
(λ (n)
(if (zero? n) 1
(* n (fact (- n 1)))
))))
)
Recursion without state
Y and Turing combinators show that languages can be made only with function definitions and function applications.
(define Y
(λ (p) (
(λ (f) (p (f f)))
(λ (f) (p (f f)))
))
)
(define Turing
(λ (p) (
(λ (f) (p (λ (a) ((f f) a))))
(λ (f) (p (λ (a) ((f f) a))))
))
)
By Renato Cordeiro Ferreira
Presentation about recursive strutures for the course MAC0316 (Fundamental Concepts in Programming Languages) at IME-USP
Scientific Programmer @ JADS | PhD Candidate @ USP | Co-founder & Coordinator @CodeLab