998 words
5 minutes
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())));
}
}
Lambda Calculus via C# (23) Y Combinator, And Divide
https://dixin.github.io/posts/lambda-calculus-via-c-sharp-23-y-combinator-and-divide/
Author
Dixin
Published at
2018-11-23
License
CC BY-NC-SA 4.0