lambda calculus – Tried to derive the Z combinator and instead derived another


I was working to derive the Z-Combinator by starting with the factorial function and ended up deriving a different fixed-point combinator. What did I derive? Did I make a subtle mistake?

Here are the steps I performed (in JavaScript)

1. Declare factorial function

let fact = n =>
    n < 2 ? 1 : n * fact(n - 1)

2. Convert to combinator (closed expression)

let fact = (self, n) =>
    n < 2 ? 1 : n * self(n - 1)

3. Thread self call

Based on signature fact(?, 7), passing fact as first argument seems reasonable fact(fact,7). So thread the parameter through the tail call:

let fact = (self, n) =>
    n < 2 ? 1 : n * self(self, n - 1)

Usage is now fact(fact,7)5040

4. Refactor to curried form

let fact = self =>
    n => n < 2 ? 1 : n * self(self)(n - 1)

5. Move self application to local declaration

let fact = self => {
    let f = n => self(self)(n)
    return n => n < 2 ? 1 : n * f(n - 1)
}

6. Convert let declaration to lambda expression

let fact = self =>
    (f =>
        n => n < 2 ? 1 : n * f(n - 1)
    )(
        n => self(self)(n)
    )

Usage is still fact(fact)(7)5040

7. Separate the factorial expression

let _fact = f => n =>
    n < 2 ? 1 : n * f(n - 1)

let fact = self =>
    (
        _fact
    )(
        n => self(self)(n)
    )

8. Move self-application from caller to body

let _fact =
    f => n => n < 2 ? 1 : n * f(n - 1)

let fact = (() => {
    let innerFact = self =>
        (
            _fact
        )(
            n => self(self)(n)
        )
    return innerFact(innerFact)
})()

Usage is now fact(7)5040

9. Convert let declaration to lambda expression

let _fact =
    f => n => n < 2 ? 1 : n * f(n - 1)

let fact = (() => {
    return (
        innerFact => innerFact(innerFact)
    )(
        self => (_fact)(n => self(self)(n))
    )
})()

10. Simplify expression

let _fact =
    f => n => n < 2 ? 1 : n * f(n - 1)

let fact =
    (innerFact => innerFact(innerFact))
    (self => (_fact)(n => self(self)(n)))

Sanity check. Usage is still fact(7)5040

11. Rename variables

The usage of innerFact and self look suspiciously similar. Rename to the same variable to discover a pattern. Separate lexical scopes so safe to do:

let _fact =
    f => n => n < 2 ? 1 : n * f(n - 1)

let fact =
    (u => u(u))
    (u => (_fact)(n => u(u)(n)))

12. Abstract _fact usage and rename fact

Rename fact to setup and abstract _fact in body by replacing with parameter f

let _fact =
    f => n => n < 2 ? 1 : n * f(n - 1)

let setup = f =>
    (u => u(u))
    (u => (f)(n => u(u)(n)))

let fact = setup(_fact)

No need for separate _fact declaration so inline:

let setup = f =>
    (u => u(u))
    (u => (f)(n => u(u)(n)))

let fact = setup(
    f => n => n < 2 ? 1 : n * f(n - 1)
)

13. Rename setup

Rename it to what? What combinator is this? According to Wikipedia The Z combinator is:

let Z = f => 
    (u => f(v => u(u)(v)))
    (u => f(v => u(u)(v)))

But what I’ve derived is:

let setup = f =>
    (u => u(u))
    (u => (f)(n => u(u)(n)))

Defining fact in terms of either seems equivalent in behavior. Did I make a mistake? Did I accidentally rediscover another well-known combinator?