718 words
4 minutes
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)));
}
}
Lambda Calculus via C# (21) SKI Combinator Calculus
https://dixin.github.io/posts/lambda-calculus-via-c-sharp-21-ski-combinator-calculus/
Author
Dixin
Published at
2018-11-21
License
CC BY-NC-SA 4.0