Skip to content
Virtue Codes
Homepage

Road to the Y Combinator, Part II

Code3 min read

In Part I, we introduced a model for computation based on pure, anonymous functions, and started our journey to the Y Combinator. In this episode, we'll take a look at some functional refactorings to equip ourselves for the journey.

Sophistry and Parlour Tricks

In order to complete this transformation, we'll have to do a number of refactorings. Please note that some of these superpowers will only work in our sandbox because we are only playing with certain toys.

Change of name

Probably the simplest refactoring is to just change a name, and since all our names will be from function signatures, changing foo to bar will look like the following, in which all instances of the name foo which are not bound to a different foo will be changed to bar:

(foo) => (
(baz) => (
(foo) => (foo(foo))
)
)('aFoo')

=>

(bar) => (
(baz) => (
(foo) => (foo(foo))
)
)('aFoo')

Note that the inner function expression with its own argument foo was not updated, since that variable shadows the outer one with the same name. We will avoid shadowing outer names in this exercise.

Tennent Function

Any expression <expr> may be substituted with a function that returns <expr> and is immediately invoked. For example:

2 + 3

=>

(() => 2 + 3)()

It should be noted that, as with any other refactoring, the inverse of this refactoring, ie. removing a Tennent Function by replacing it with the expression it returns, is just as valid.

Add Binding

Simply add a parameter name to a function, and supply a value within the parentheses where it is invoked. Since the parameter isn't used yet, the value of the argument doesn't matter.

((foo) => { some: 'expr' })('aFoo')

=>

((foo, bar) => { some: 'expr' })('aFoo', 'aBar')

Note that bar is a legal name, as long as there are no instances of bar in the function body which are free with respect to the function that receives the new binding. For our purposes, it will be fine to just pick a new name that doesn't occur in the program.

Wrap Function

Sometimes it is useful to compute something, but only when it is needed. This is called lazy evaluation. Wrap an expression whose value is a function, with a function that takes and passes the same number of arguments to that expression:

(x) => 1

=>

(v) => ((x) => 1)(v)

Inline Value

Anywhere a name is used, the value to which the name is bound can be substituted for that name:

(cheese) => (
(olives, sauce) =>
makePizza(
sauce,
olives,
cheese
)
)('Cheddar')

=>

(cheese) => (
(olives, sauce) =>
makePizza(
sauce,
olives,
'Cheddar'
)
)('Cheddar')

Now cheese is an unused binding; it could be removed or renamed as needed. We'll do the inverse of this refactoring also.

Summary

In this episode, we covered some functional refactorings in order to equip ourselves to derive the Y Combinator. In Part III, we'll express our recursion in pure anonymous functions, or lambdas.