Lambda Calculus via C# (23) Y Combinator, And Divide

[LINQ via C# series]

[Lambda Calculus via C# series]

Latest version: https://weblogs.asp.net/dixin/lambda-calculus-via-csharp-7-fixed-point-combinator-and-recursion

Fix point

p is the fixed point of function F if and only if:

  p
≡ F p

The following picture is stolen from Wikipedia:

A simple example:

F := 0 - x

has a fixed point 0:

  0
≡ F 0

The above fixed point definition also leads to:

  p
≡ F p
≡ F (F p)
≡ ...
≡ F (F (F … (F p) …))

Fixed point combinator

In lambda calculus and combinatory logic, Y combinator is a fixed point combinator:

Y := λf.(λx.f (x x)) (λx.f (x x))

It is called so because it calculates a function F’s fixed point Y F.

According to the above definition of fixed point p ≡ F p, there is:

  (Y F)
≡ F (Y F)

Proof:

  Y F
≡ (λf.(λx.f (x x)) (λx.f (x x))) F
≡ (λx.F (x x)) (λx.F (x x))
≡ F ((λx.F (x x)) (λx.F (x x)))
≡ F (Y F)

Y combinator was discovered by Haskell Curry.

y_combinator

As a fixed point combinator, Y also has the same property of:

  Y F
≡ F (Y F)
≡ F (F (Y F))
≡ ...
≡ F (F (F … (F (Y F)) …))

So Y can be used to implement recursion.

390px-Knights_of_the_Lambda_Calculus.svg

And this is Y in SKI:

Y2 := S (K (S I I)) (S (S (K S) K) (K (S I I)))

or just in SK:

Y3 := S S K (S (K (S S (S (S S K)))) K)

And in C#:

public delegate Func<T, TResult> Recursion<T, TResult>(Recursion<T, TResult> f);

public static class YCombinator
{
    // Y = λf.(λx.f(x x)) (λx.f(x x))
    // Y = f => (λx.f(x x)) (λx.f(x x))
    // Y = f => (x => f(x(x)))(x => f(x(x)))
    // Y = (x => arg => f(x(x))(arg))(x => arg => f(x(x))(arg))
    public static Func<T, TResult> Y<T, TResult>
        (Func<Func<T, TResult>, Func<T, TResult>> f) => 
            new Recursion<T, TResult>(x => arg => f(x(x))(arg))(x => arg => f(x(x))(arg));
}

Recursion

As explaned in the part of Church numeral arithmetic, recursion cannot be implemented directly in lambda calculus.

Example - factorial

The factorial function can be intuitively implemented by recursion. In C#:

Func<uint, uint> factorial = null; // Must have. So that factorial can recursively refer itself.
factorial = x => x == 0U ? 1U : factorial(x - 1U);

But in lambda calculus:

λn.If (IsZero n) (λx.1) (λx.Self (Decrease n))

An anonymous function cannot directly refer itself by its name in the body.

With Y, the solution is to create a helper to pass “the algorithm itself” as a parameter. So:

FactorialHelper := λf.λn.If (IsZero n) (λx.1) (λx.f (Decrease n))

Now Y can be applied with the helper:

Y FactorialHelper n

So:

Factorial := Y FactorialHelper
           ≡ Y (λf.λn.If (IsZero n) (λx.1) (λx.f (Decrease n)))

In C# lambda calculus:

public static partial class _NumeralExtensions
{
    // Factorial = factorial => numeral => If(numeral.IsZero())(_ => One)(_ => factorial(numeral.Decrease()));
    public static Func<_Numeral, _Numeral> Factorial
        (Func<_Numeral, _Numeral> factorial) => numeral =>
            ChurchBoolean.If<_Numeral>(numeral.IsZero())
                (_ => One)
                (_ => factorial(numeral.Decrease()));

    public static _Numeral Factorial
        (this _Numeral numeral) => YCombinator.Y<_Numeral, _Numeral>(Factorial)(numeral);
}

Example - Fibonacci

Another recursion example is Fibonacci:

Func<uint, uint> fibonacci = null; // Must have. So that fibonacci can recursively refer itself.
fibonacci = x => x > 1U ? fibonacci(x - 1U) + fibonacci(x - 2U) : x;

The recursion cannot be done in anonymous function either:

λn.If (IsGreater n 1) (λx.Add (Self (Subtract n 1)) (Self (Subtract n 2))) (λx.n)

The same solution can be used - create a helper to pass “the algorithm itself” as a parameter:

FibonacciHelper := λf.λn.If (IsGreater n 1) (λx.Add (f (Subtract n 1)) (f (Subtract n 2))) (λx.n)

Application to Y will be the same way too:

Y FibonacciHelper n

So:

Fibonacci := Y FibonacciHelper
           ≡ Y (λf.λn.If (IsGreater n 1) (λx.Add (f (Subtract n 1)) (f (Subtract n 2))) (λx.n))

C#:

public static partial class _NumeralExtensions
{
    // Fibonacci  = fibonacci  => numeral => If(numeral > One)(_ => fibonacci(numeral - One) + fibonacci(numeral - One - One))(_ => numeral);
    public static Func<_Numeral, _Numeral> Fibonacci
        (Func<_Numeral, _Numeral> fibonacci) => numeral =>
            ChurchBoolean.If<_Numeral>(numeral > One)
                (_ => fibonacci(numeral - One) + fibonacci(numeral - One - One))
                (_ => numeral);

    public static _Numeral Fibonacci
        (this _Numeral numeral) => YCombinator.Y<_Numeral, _Numeral>(Fibonacci)(numeral);
}

DivideBy

In the Church numeral arithmetic, this (cheating) recursive _DivideBy was temporarily used:

_DivideBy := λa.λb.If (IsGreaterOrEqual a b) (λx.Add One (_DivideBy (Subtract a b) b)) (λx.Zero)

Finally, with Y, a real DivideBy in lambda calculus can be defined:

DivideByHelper := λf.λa.λb.If (IsGreaterOrEqual a b) (λx.Add One (f (Subtract a b) b)) (λx.Zero)

DivideBy := Y DivideByHelper
          ≡ Y (λf.λa.λb.If (IsGreaterOrEqual a b) (λx.Add One (f (Subtract a b) b)) (λx.Zero))

Once again, just create a helper to pass itself as a parameter to implement recursion, as easy as Factorial and Fibonacci.

C#:

public static partial class _NumeralExtensions
{
    // DivideBy = divideBy => dividend => divisor => If(dividend >= divisor)(_ => One + divideBy(dividend - divisor)(divisor))(_ => Zero)
    public static Func<_Numeral, Func<_Numeral, _Numeral>> DivideBy
        (Func<_Numeral, Func<_Numeral, _Numeral>> divideBy) => dividend => divisor =>
            ChurchBoolean.If<_Numeral>(dividend >= divisor)
                (_ => One + divideBy(dividend - divisor)(divisor))
                (_ => Zero);

    public static _Numeral DivideBy
        (this _Numeral dividend, _Numeral divisor) =>
            YCombinator.Y<_Numeral, Func<_Numeral, _Numeral>>(DivideBy)(dividend)(divisor);
}

Notice a difference here: Factorial and Fibonacci both takes 1 parameter, but DivideBy takes 2 parameters - dividend, divisor. However, with currying, Y<T, TResult> can just be closed type Y<X, Func<Y, Z>>, so that this difference is nicely and easily handled.

Unit tests

[TestClass()]
public class _NumeralExtensionsTests
{
    [TestMethod()]
    public void FactorialTest()
    {
        Func<uint, uint> factorial = null; // Must have. So that factorial can recursively refer itself.
        factorial = x => x == 0U ? 1U : factorial(x - 1U);

        Assert.IsTrue(factorial(0U) == 0U._Church().Factorial());
        Assert.IsTrue(factorial(1U) == 1U._Church().Factorial());
        Assert.IsTrue(factorial(2U) == 2U._Church().Factorial());
        Assert.IsTrue(factorial(3U) == 3U._Church().Factorial());
        Assert.IsTrue(factorial(10U) == 10U._Church().Factorial());
    }

    [TestMethod()]
    public void FibonacciTest()
    {
        Func<uint, uint> fibonacci = null; // Must have. So that fibonacci can recursively refer itself.
        fibonacci = x => x > 1U ? fibonacci(x - 1U) + fibonacci(x - 2U) : x;

        Assert.IsTrue(fibonacci(0U) == 0U._Church().Fibonacci());
        Assert.IsTrue(fibonacci(1U) == 1U._Church().Fibonacci());
        Assert.IsTrue(fibonacci(2U) == 2U._Church().Fibonacci());
        Assert.IsTrue(fibonacci(3U) == 3U._Church().Fibonacci());
        Assert.IsTrue(fibonacci(10U) == 10U._Church().Fibonacci());
    }

    [TestMethod()]
    public void DivideByTest()
    {
        Assert.IsTrue(1U / 1U == (1U._Church().DivideBy(1U._Church())));
        Assert.IsTrue(1U / 2U == (1U._Church().DivideBy(2U._Church())));
        Assert.IsTrue(2U / 2U == (2U._Church().DivideBy(2U._Church())));
        Assert.IsTrue(2U / 1U == (2U._Church().DivideBy(1U._Church())));
        Assert.IsTrue(10U / 3U == (10U._Church().DivideBy(3U._Church())));
        Assert.IsTrue(3U / 10U == (3U._Church().DivideBy(10U._Church())));
    }
}

4 Comments

Add a Comment

As it will appear on the website

Not displayed

Your website