Lambda Calculus via C# (21) SKI Combinator Calculus

[LINQ via C# series]

[Lambda Calculus via C# series]

Latest version: https://weblogs.asp.net/dixin/lambda-calculus-via-csharp-6-combinatory-logic

The previous part shows SKI calculus is untyped and strongly typed C# implementation does not work. So here comes the SKI in untyped C#:

public static partial class SkiCombinators
{
    public static Func<dynamic, Func<dynamic, Func<dynamic, dynamic>>>
        S = x => y => z => x(z)(y(z));

    public static Func<dynamic, Func<dynamic, dynamic>>
        K = x => _ => x;

    public static Func<dynamic, dynamic>
        I = x => x;
}

Notice closed types (Func<dynamic, …>) are used instead of open type (Func<T, …>) in previous part. So S, K and I do not have to be in the form of C# methods.

I Combinator

Actually I can be defined with S and K:

  S K K x
≡ K x (K x)
≡ x

  S K S x
≡ K x (S x)
≡ x

So I is merely syntactic sugar:

I2 := S K K
I3 := S K S

And C#:

public static partial class SkiCombinators
{
    public static Func<dynamic, dynamic>
        I2 = S(K)(K);
        
    public static Func<dynamic, dynamic>
        I3 = S(K)(S);
}

BCKW combinators

BCKW and SKI can define each other:

B := S (K S) K
C := S (S (K (S (K S) K)) S) (K K)
K := K
W := S S (S K)

S := B (B (B W) C) (B B) ≡ B (B W) (B B C)
K := K
I := W K

ω combinator

In SKI, the self application combinator ω is:

ω := S I I

This is easy to understand:

  S I I x
≡ I x (I x) 
≡ x x

Then

Ω := S I I (S I I) 
   ≡ I (S I I) (I (S I I)) 
   ≡ (S I I) (S I I) 
   ≡ S I I (S I I)
   ...

C#:

public static partial class SkiCombinators
{
    public static Func<dynamic, dynamic> 
        ω = S(I)(I);

    public static Func<dynamic, dynamic>
        Ω = _ => ω(ω); // Ω = ω(ω) throws exception.
}

Function composition

Remember function composition:

(f2 ∘ f1) x := f2 (f1 x)

In SKI:

  S (K S) K f1 f2 x
≡ (K S) f1 (K f1) f2 x
≡ S (K f1) f2 x
≡ (K f1) x (f2 x)
≡ f1 (f2 x)

So:

Compose := S (K S) K

In C#:

public static partial class SkiCombinators
{
    public static Func<dynamic, dynamic> 
        Compose = S(K(S))(K);
}

Booleans

From previous part:

True := K
False := S K

So:

public static partial class SkiCombinators
{
    public static Boolean
        True = new Boolean(K);
        
    public static Boolean
        False = new Boolean(S(K));
}

Numerals

Remember:

0 := λf.λx.x
1 := λf.λx.f x
2 := λf.λx.f (f x)
3 := λf.λx.f (f (f x))
...

In SKI:

  K I f x
≡ I x
≡ x

  I f x
≡ f x

  S Compose I f x
≡ Compose f (I f) x
≡ Compose f f x
≡ f (f x)

  S Compose (S Compose I) f x
≡ Compose f (S Compose I f) x
≡ Compose f (Compose f f) x
≡ f (f (f x))

...

So:

0 := K I                     ≡ K I
1 := I                       ≡ I
2 := S Compose I             ≡ S (S (K S) K) I
3 := S Compose (S Compose I) ≡ S (S (K S) K) (S (S (K S) K) I)
...

In C#:

public static partial class SkiCombinators
{
    public static Func<dynamic, dynamic> 
        Zero = K(I);

    public static Func<dynamic, dynamic> 
        One = I;

    public static Func<dynamic, dynamic> 
        Two = S(Compose)(I);

    public static Func<dynamic, dynamic> 
        Three = S(Compose)(S(Compose)(I));
}

And generally:

Increase := S Compose ≡ S (S (K S) K)

C#:

public static partial class SkiCombinators
{
    public static Func<dynamic, Func<dynamic, dynamic>> 
        Increase = S(Compose);
}

The encoding can keep going, but this post stops here. Actually, S and K can be composed to combinators that are extensionally equal to any lambda term. The proof can be found here - Completeness of the S-K basis.

Unit tests

[TestClass]
public class SkiCombinatorsTests
{
    [TestMethod]
    public void SkiTests()
    {
        Func<int, Func<int, int>> x1 = a => b => a + b;
        Func<int, int> y1 = a => a + 1;
        int z1 = 1;
        Assert.AreEqual(x1(z1)(y1(z1)), (int)SkiCombinators.S(x1)(y1)(z1));
        Assert.AreEqual(x1, (Func<int, Func<int, int>>)SkiCombinators.K(x1)(y1));
        Assert.AreEqual(x1, (Func<int, Func<int, int>>)SkiCombinators.I(x1));
        Assert.AreEqual(y1, (Func<int, int>)SkiCombinators.I(y1));
        Assert.AreEqual(z1, (int)SkiCombinators.I(z1));

        string x2 = "a";
        int y2 = 1;
        Assert.AreEqual(x2, (string)SkiCombinators.K(x2)(y2));
        Assert.AreEqual(x2, (string)SkiCombinators.I(x2));
        Assert.AreEqual(y2, (int)SkiCombinators.I(y2));
    }

    [TestMethod]
    public void BooleanTests()
    {
        Assert.AreEqual(true, (bool)SkiCombinators.True(true)(false));
        Assert.AreEqual(false, (bool)SkiCombinators.False(new Func<dynamic, dynamic>(_ => true))(false));
    }

    [TestMethod]
    public void NumeralTests()
    {
        Assert.AreEqual(0U, SkiCombinators._UnchurchNumeral(SkiCombinators.Zero));
        Assert.AreEqual(1U, SkiCombinators._UnchurchNumeral(SkiCombinators.One));
        Assert.AreEqual(2U, SkiCombinators._UnchurchNumeral(SkiCombinators.Two));
        Assert.AreEqual(3U, SkiCombinators._UnchurchNumeral(SkiCombinators.Three));
        Assert.AreEqual(4U, SkiCombinators._UnchurchNumeral(SkiCombinators.Increase(SkiCombinators.Three)));
    }
}

4 Comments

Add a Comment

As it will appear on the website

Not displayed

Your website