S and K
Context
Moses Schönfinkel's inspiration came from the recently discovered result that all of the known operators in propositional logic (~a
, a && b
, a || b
, a => b
and a <=> b
) could be reduced to / derived from a single operator: nand
.
Notation
Notation: a tree (xy)
can be thought as a function x
applied to an argument y
. When evaluated, the tree returns a value. The function, argument or values are either combinators or binary trees.
S and K
K
The k
combinator is: k[x][y] = x
S
The s
and k
combinators: s[x][y][z] = x[z][y[z]]
and
Example
For example, let's say we start from s[k][s][k]
. This evaluates to k[k][s[k]]
by applying the s
rule with x = k
, y = s
and z = k
. This then evalutes to k
as we apply the k
rule with x = k
and y = s[k]
. Since there is no more rule to apply, it halts.
Boolean logic
We pick very specific definitions of true
and false
, such that we can construct an
if-then-else structure: when true
follow the then
, when false
follow the else
.
And from that, we define other boolean combinators to perform the task that we want them to perform.
True
We define T[x][y]
= T = K
, which because K[x][y]
= x
, and,
False
We define F[x][y]
= F[x][y]
= S[K][x][y]
, which is K[y][x[y]]
= y
. With that, then,
NOT
We define NOT[x]
as S[S[I][K[F]]][K[T]][x]
, which leads to S[I][K[F]][x][K[T][x]]
= I[x][K[F][x]][T]
= x[F][T]
.
For example, NOT[T]
= T[F][T]
= F
, NOT[F]
= F[F][T]
= T
:
OR
OR[x][y]
= S[I][K[T]][x][y]
= I[x][K[T][x]][y]
= x[T][y]
For example: OR[T][T]
= T[T][T]
= T
, OR[T][F]
= T[T][F]
= T
, OR[F][T]
= F[T][T]
= T
and OR[F][F]
= F[F][F]
= F
.
AND
AND[x][y]
= S[S][K[K[F]]][x][y]
= S[x][K[K[F]][x]][y]
= x[y][K[K[F]][x][y]]
= x[y][K[F][y]]
= x[y][F]
, e.g. AND[T][T]
= T[T][F]
= T
, AND[T][F]
= T[F][F]
= F
, AND[F][T]
= F[T][F]
= F
and AND[F][F]
= F[F][F]
= F
.
NAND
You can combine combinators (duh) to form composite constructions, for example a NAND[x][y]
= NOT[AND[x][y]]
= AND[x][y][F][T]
= x[y][F][F][T]
.
For example, NAND[T][T]
= T[T][F][F][T]
= T[F][T]
= F
, NAND[F][T]
= F[T][F][F][T]
= F[F][T]
= T
, NAND[T][F]
= T[F][F][F][T]
= F[F][T]
= T
and NAND[F][F]
= F[F][F][F][T]
= F[F][T]
= T
.
Control
Identity
I[x]
: S[K][x][y]
always evaluates to y
in two steps (S[K][x][y]
= K[y][x[z]]
= y
). S[K][x][y]
and I[y]
are functionally equivalent because they always yield the same result when applied to any y
.
Second
N[x][y]
is defined as N = K[I]
and results in y
, since N[x][y]
= K[i][x][y]
= I[y]
= y
, which complements K
(which selects the first parameter, rather than the second).
Functions
Composition
B[f][g][x]
=f[g[x]]
performs a composition of functions (i.e.f * g (x)
=f(g(x))
), and is defined asB[f][g][x]
=S[K[S]][K][f][g][x]
=K[S][f][K[f]][g][x]
=S[K[f]][g][x]
=K[f][x][g[x]]
=f[g[x]]
.
Currying
C[f][g][x]
=f[x][g]
, defined asC[f][g][x]
=S[B[B][S]][K[K]][f][g][x]
=B[B][S][f][K[K][f]][g][x]
=B[S[f]][K[K][f]][g][x]
=S[f][K[K][f][g]][x]
=f[x][K[K][f][g][x]]
=f[x][K[g][x]]
=f[x][g]
W
W[x][f]
=f[x]
takes the second parameter and applies it to the first, which is constructed asW[x][f]
=C[I][x][f]
=I[f][x]
=f[x]
.
V
V[x][y][z]
= z[x][y]
takes the third argument, applies it to the first argument and then to the second. It is is defined as B[C][W][x][y][z]
= C[W[x]][y][z]
= W[x][z][y]
= z[x][y]
.
Data Structures
Pair
Suppose we want to describe ordered pairs, say (x, y)
, we can define a combinator Pair[x][y]
such that First[Pair[x][y]]
= x
and Second[Pair[x][y]]
= y
. If we define Pair[x][y][z]
= V[x][y][z]
= z[x][y]
and First
= W[T]
and Second
= W[F]
.
For example, First[Pair[x][y]]
= W[T][Pair[x][y]]
= Pair[x][y][T]
= T[x][y]
= x
and Second[Pair[x][y]]
= W[F][Pair[x][y]]
= Pair[x][y][F]
= F[x][y]
= y
.
Natural Numbers
Zero
We want to make the function Zero[x]
return T
when x
= 0
and F
otherwise, so we define Zero[x]
= First[x]
. Since we want Zero[0]
= First[0]
= W[T][0]
= 0[T]
. If we define 0
= I
, then Zero[0]
= 0[T]
= I[T]
= T
. So, we define 0
= I
.
1, 2, 3, ...
With the representation of 0
in place, we can define 1
as the pair Pair[F][0]
, 2
= Pair[F][1]
, 3
= Pair[F][2]
, and so on.
We can then define Next[n]
= Pair[false][n]
, which can give us our natural numbers 0
, Next[0]
, Next[Next[0]]
, Next[Next[Next[0]]]
, etc.
The Previous[n]
= Second[n]
, for example Previous[2]
= Second[2]
= Second[Pair[F][1]]
= 1
.
Recursion
Arithmetic
Apply
We'd like to derive a combinator that can compute the application of a function n
times, e.g. Apply[0][f][x]
= x
, Apply[1][f][x]
= f(x)
, Apply[2][f][x]
= f[f[x]]
, and so on.
Add
For example, adding two numbers, m
and n
is the same as applying the function Next
m
times to n
. That is, Add[m][n]
= Apply[m][Next][n]
= Next[Next[Next[...[Next[n]]]]]
(embeds m
times).
We define Apply[n][f][x]
as Zero[n][x][f[Apply[Previous[n]][f][x]]]
, which in practice can read as:
Apply(n, f, x) {
if (Zero(n) == T) {
return x;
} else {
return f(Apply(Previous(n), f, x));
}
}
Sub
Much like Add, we can define Sub as Sub[m][n] = Apply[m][Previous][n]
.
Fib
Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...
NextPair[p] = Pair[Second[p]][Add[First[p]][Second[p]]]
. For example, NextPair[Pair[0][1]]
= Pair[Second[Pair[0][1]]][Add[First[Pair[0][1]]][Second[Pair[0][1]]]] = Pair[1][Add[0][1]]
= Pair[1][1]
.
Iota
-
i[x]
=x[S][K]
-
I[x]
=i[i][x]
=i[S][K][x]
=S[S][K][K][x]
=S[K][K[K]][x]
=K[x][K[K][x]]
=x
. -
K[x][y]
=i[i[I]][x][y]
=i[i[i[i]]][x][y]
=i[i[i[S][K]]][x][y]
=i[i[S][S][K][K]]][x][y]
=i[S[S][K][S][K][K]][x][y]
=S[S][K][S][K][K][S][K][x][y]
=S[S][K[S]][K][K][S][K][x][y]
=S[K][K[S][K]][S][K][x][y]
=S[K][S][S][K][x][y]
=K[S][S[S]][K][x][y]
=S[K][S[S][K]][x][y]
=K[x][S[S][K][y]]
=x
. -
S[x][y][z]
=i[K][x][y][z]
=K[S][K][x][y][z]
=S[x][y][z]
=x[z][y[z]]
-
i[x] => x[[a][b][c] => a[c][b[c]]]][[d][e] => d]
-
i[i][x] => i[[a][b][c] => a[c][b[c]]]][[d][e] => d][x]
=([a][b][c] => a[c][b[c]])[[a][b][c] => a[c][b[c]]]][[d][e] => d]][[d][e] => d][x]
=([a][b][c] => a[c][b[c]])[[d][e] => d][([d][e] => d)[[d][e] => d]][x]
=([d][e] => d)[x][([d][e] => d)[[d][e] => d][x]]
=x
.
Alternatively, j
would also work:
j[x] = x[K][S]
S
=j[j][j]
=j[K][S][j]
=K[K][S][S][j]
=K[S][j]
=S
K
=j[j[j][j][j][j]]
=j[S[j][j]]
=S[j][j][K][S]
=j[K][j[K]][S]
=K[K][S][j[K]][S]
=K[j[K]][S]
=j[K]
=K[K][S]
=K
;
Another option is W[x] = x[K][S][K]
with K = W[W][W]
and S = W[W[W]]
.
K
=W[W][W]
=W[K][S][K][W]
=K[K][S][K][S][K][W]
=K[K][S][K][W]
=K[K][W]
=K
S
=W[W[W]]
=W[W[K][S][K]]
=W[K[K][S][K][S][K]]
=K[K][S][K][S][K][K][S][K]
=K[K][S][K][K][S][K]
=K[K][K][S][K]
=K[S][K]
=S
.
If we define m
as:
-
m[x][y][z]
=x[z][y[K[z]]]
-
K = m[m[m]][ m[m[m]][m][m] [m][m][m]]
=m[m[m]][ m[m][m][m[K[m]]] [m][m][m]]
=m[m[m]][ m[m[K[m]]][m[K[m[K[m]]]]] [m][m][m]]
-
S = m (m (m m (m m (m m))(m (m (m m (m m)))))) m m
Unused?
Self Application
M[x]
(sometimes also called u
, e.g. here) is a combinator that takes an argument and applies that argument to itself: M[x]
= S[I][I][x]
= I[x][I[x]]
= x[x]
An interesting property of the M
combinator is that its self-application is irreducible: u[u]
= s[i][i][s[i][i]]
= i[s[i][i]][i[s[i][i]]]
= s[i][i][s[i][i]]
= u[u]
.
Reversal
R[x][y]
= y[x]
can be constructed as R[x][y]
= S[K[S[I]]][K][x][y]
= K[S[I]][x][K[x]][y]
= S[I][K[x]][y]
= I[y][K[x][y]]
= y[K[x][y]]
= y[x]
.
References
- Combinatory Fibonacci
- TODO: look at Iota and Jot which can derive S and K
- SKI Calculus
- Moses Schönfinkel
- Combinators and the History of Computation
- Combinators: A 100-Year Celebration