The SKI combinator calculus is a combinatory logic system and a computational system. It can be thought of as a computer programming language, though it is not convenient for writing software. Instead, it is important in the mathematical theory of algorithms because it is an extremely simple Turing complete language. It can be likened to a reduced version of the untyped lambda calculus. It was introduced by Moses Schönfinkel[1] and Haskell Curry.[2]
All operations in lambda calculus can be encoded via abstraction elimination into the SKI calculus as binary trees whose leaves are one of the three symbols S, K, and I (called combinators).
Although the most formal representation of the objects in this system requires binary trees, for simpler typesetting they are often represented as parenthesized expressions, as a shorthand for the tree they represent. Any subtrees may be parenthesized, but often only the right-side subtrees are parenthesized, with left associativity implied for any unparenthesized applications. For example, ISK means ((IS)K). Using this notation, a tree whose left subtree is the tree KS and whose right subtree is the tree SK can be written as KS(SK). If more explicitness is desired, the implied parentheses can be included as well: ((KS)(SK)).
Informally, and using programming language jargon, a tree (xy) can be thought of as a function x applied to an argument y. When evaluated (i.e., when the function is "applied" to the argument), the tree "returns a value", i.e., transforms into another tree. The "function", "argument" and the "value" are either combinators or binary trees. If they are binary trees, they may be thought of as functions too, if needed.
The evaluation operation is defined as follows:
(x, y, and z represent expressions made from the functions S, K, and I, and set values):
I returns its argument:
Ix = x
K, when applied to any argument x, yields a one-argument constant function Kx, which, when applied to any argument y, returns x:
Kxy = x
S is a substitution operator. It takes three arguments and then returns the first argument applied to the third, which is then applied to the result of the second argument applied to the third. More clearly:
Sxyz = xz(yz)
Example computation: SKSK evaluates to KK(SK) by the S-rule. Then if we evaluate KK(SK), we get K by the K-rule. As no further rule can be applied, the computation halts here.
For all trees x and all trees y, SKxy will always evaluate to y in two steps, Ky(xy) = y, so the ultimate result of evaluating SKxy will always equal the result of evaluating y. We say that SKx and I are "functionally equivalent" for any x because they always yield the same result when applied to any y.
From these definitions it can be shown that SKI calculus is not the minimum system that can fully perform the computations of lambda calculus, as all occurrences of I in any expression can be replaced by (SKK) or (SKS) or (SK x) for any x, and the resulting expression will yield the same result. So the "I" is merely syntactic sugar. Since I is optional, the system is also referred as SK calculus or SK combinator calculus.
It is possible to define a complete system using only one (improper) combinator. An example is Chris Barker's iota combinator, which can be expressed in terms of S and K as follows:
ιx = xSKIt is possible to reconstruct S, K, and I from the iota combinator. Applying ι to itself gives ιι = ιSK = SSKK = SK(KK) which is functionally equivalent to I. K can be constructed by applying ι twice to I (which is equivalent to application of ι to itself): ι(ι(ιι)) = ι(ιιSK) = ι(ISK) = ι(SK) = SKSK = K. Applying ι one more time gives ι(ι(ι(ιι))) = ιK = KSK = S.
The simplest possible term forming a basis is X = λx λy λz. x z (y (λ_.z)), which satisfies X (X X) (X (X X) X X X X X) = K, and X (X (X X (X X (X X))(X (X (X X (X X)))))) X X = S.
The terms and derivations in this system can also be more formally defined:
Terms:The set T of terms is defined recursively by the following rules.
Derivations:A derivation is a finite sequence of terms defined recursively by the following rules (where α and ι are words over the alphabet while β, γ and δ are terms):
Assuming a sequence is a valid derivation to begin with, it can be extended using these rules. All derivations of length 1 are valid derivations.
SII is an expression that takes an argument and applies that argument to itself:
SIIα = Iα(Iα) = αα
This is also known as U combinator, Ux = xx. One interesting property of it is that its self-application is irreducible:
SII(SII) = I(SII)(I(SII)) = SII(I(SII)) = SII(SII)Or, using the equation as its definition directly, we immediately get U U = U U.
Another thing is that it allows one to write a function that applies one thing to the self application of another thing:
(S(Kα)(SII))β = Kαβ(SIIβ) = α(Iβ(Iβ)) = α(ββ)or it can be seen as defining yet another combinator directly, Hxy = x(yy).
This function can be used to achieve recursion. If β is the function that applies α to the self application of something else,
β = Hα = S(Kα)(SII)then the self-application of this β is the fixed point of that α:
SIIβ = ββ = α(ββ) = α(α(ββ)) =
\ldots
If α expresses a "computational step" computed by αρν for some ρ and ν, that assumes ρν' expresses "the rest of the computation" (for some ν' that α will "compute" from ν), then its fixed point ββ expresses the whole recursive computation, since using the same function ββ for the "rest of computation" call (with ββν = α(ββ)ν) is the very definition of recursion: ρν' = ββν' = α(ββ)ν' = ... . α will have to employ some kind of conditional to stop at some "base case" and not make the recursive call then, to avoid divergence.
This can be formalized, with
β = Hα = S(Kα)(SII) = S(KS)Kα(SII) = S(S(KS)K)(K(SII)) αas
Yα = SIIβ = SII(Hα) = S(K(SII))H α = S(K(SII))(S(S(KS)K)(K(SII))) αwhich gives us one possible encoding of the Y combinator.
This becomes much shorter with the use of the B and C combinators, as the equivalent
Yα = S(KU)(SB(KU))α = U(BαU) = BU(CBU)α
or directly, as
Hαβ = α(ββ) = BαUβ = CBUαβ
Yα = U(Hα) = BU(CBU)αAnd with a pseudo-Haskell syntax it becomes the exceptionally short Y = U . (. U).
S(K(SI))K reverses the following two terms:
S(K(SI))Kαβ →
K(SI)α(Kα)β →
SI(Kα)β →
Iβ(Kαβ) →
Iβα →
βα
SKI combinator calculus can also implement Boolean logic in the form of an if-then-else structure. An if-then-else structure consists of a Boolean expression that is either true (T) or false (F) and two arguments, such that:
Txy = xand
Fxy = y
The key is in defining the two Boolean expressions. The first works just like one of our basic combinators:
T = K
Kxy = x
The second is also fairly simple:
F = SK
SKxy = Ky(xy) = y
Once true and false are defined, all Boolean logic can be implemented in terms of if-then-else structures.
Boolean NOT (which returns the opposite of a given Boolean) works the same as the if-then-else structure, with F and T as the second and third values, so it can be implemented as a postfix operation:
NOT = (F)(T) = (SK)(K)If this is put in an if-then-else structure, it can be shown that this has the expected result
(T)NOT = T(F)(T) = F
(F)NOT = F(F)(T) = T
Boolean OR (which returns T if either of the two Boolean values surrounding it is T) works the same as an if-then-else structure with T as the second value, so it can be implemented as an infix operation:
OR = T = KIf this is put in an if-then-else structure, it can be shown that this has the expected result:
(T)OR(T) = T(T)(T) = T
(T)OR(F) = T(T)(F) = T
(F)OR(T) = F(T)(T) = T
(F)OR(F) = F(T)(F) = F
Boolean AND (which returns T if both of the two Boolean values surrounding it are T) works the same as an if-then-else structure with F as the third value, so it can be implemented as a postfix operation:
AND = F = SKIf this is put in an if-then-else structure, it can be shown that this has the expected result:
(T)(T)AND = T(T)(F) = T
(T)(F)AND = T(F)(F) = F
(F)(T)AND = F(T)(F) = F
(F)(F)AND = F(F)(F) = F
Because this defines T, F, NOT (as a postfix operator), OR (as an infix operator), and AND (as a postfix operator) in terms of SKI notation, this proves that the SKI system can fully express Boolean logic.
As the SKI calculus is complete, it is also possible to express NOT, OR and AND as prefix operators:
NOT = S(SI(KF))(KT) (as S(SI(KF))(KT)x = SI(KF)x(KTx) = Ix(KFx)T = xFT)
OR = SI(KT) (as SI(KT)xy = Ix(KTx)y = xTy)
AND = SS(K(KF)) (as SS(K(KF))xy = Sx(K(KF)x)y = xy(KFy) = xyF)
The combinators K and S correspond to two well-known axioms of sentential logic:
,
.
Function application corresponds to the rule modus ponens:
from and, infer .
The axioms AK and AS, and the rule MP are complete for the implicational fragment of intuitionistic logic. In order for combinatory logic to have as a model:
This connection between the types of combinators and the corresponding logical axioms is an instance of the Curry–Howard isomorphism.
There may be multiple ways to do a reduction. All are equivalent, as long as you don't break order of operations
rm{SKI(KIS)}
rm{SKI(KIS)} ⇒ rm{K(KIS)(I(KIS))} ⇒ rm{KIS} ⇒ rm{I}
rm{SKI(KIS)} ⇒ rm{SKII} ⇒ rm{KI(II)} ⇒ rm{KII} ⇒ rm{I}
rm{KS(I(SKSI))}
rm{KS(I(SKSI))} ⇒ rm{KS(I(KI(SI)))} ⇒ rm{KS(I(I))} ⇒ rm{KS(II)} ⇒ rm{KSI} ⇒ rm{S}
rm{KS(I(SKSI))} ⇒ rm{S}
rm{SKIK} ⇒ rm{KK(IK)} ⇒ rm{KKK} ⇒ rm{K}