SKI combinator calculus

Short description: Simple Turing complete logic

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).

## Contents

* 1 Notation
* 2 Informal description
* 3 Formal definition
* 4 Conversion of lambda terms to SKI combinators
* 5 SKI expressions
* 5.1 Self-application and recursion
* 5.2 The reversal expression
* 5.3 Boolean logic
* 6 Connection to intuitionistic logic
* 7 Examples of reduction
* 8 See also
* 9 References
* 10 External links

## Notation

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)).

## Informal description

Informally, and using programming language jargon, a tree (xy) can be thought of as an application of the function x 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 with application nodes. 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 combinators S, K, and I, and possibly variables standing for some as yet unspecified SKI expressions):

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 be the same as 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, while being a minimalistic system, can fully perform any computations of the lambda calculus. 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 to 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 = xSK = S(λx.xS)(λx.K)x = S(S(λx.x)(λx.S))(KK)x = S(SI(KS))(KK)x

It 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[clarification needed] term forming a basis is X = λf.f (λxyz.x z (y z)) (λxyz.x), which satisfies X X = K, and X (X X) = S.

## Formal definition

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.

1. S, K, and I are terms.
2. If τ1 and τ2 are terms, then (τ1τ2) is a term.
3. Nothing is a term if not required to be so by the first two rules.

Derivations: A derivation is a finite sequence of terms defined recursively by the following rules (where α and ι are words over the alphabet {S, K, I, (, )} while β, γ and δ are terms):

1. If Δ is a derivation ending in an expression of the form α(Iβ)ι, then Δ followed by the term αβι is a derivation.
2. If Δ is a derivation ending in an expression of the form α((Kβ)γ)ι, then Δ followed by the term αβι is a derivation.
3. If Δ is a derivation ending in an expression of the form α(((Sβ)γ)δ)ι, then Δ followed by the term α((βδ)(γδ))ι is a derivation.

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.

## Conversion of lambda terms to SKI combinators

An expression in the lambda calculus can be converted into an SKI combinator calculus expression in accordance with the following rules:

1. λx. x = I
2. λx. c = Kc (provided that c does not depend on x)
3. λx. c x = c (provided that c does not depend on x)
4. λx. y z = S(λx.y)(λx.z)

## SKI expressions

### Self-application and recursion

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 Ux = xx 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β = ββ = α(ββ) = α(α(ββ)) = …

Or, directly again from the derived definition, Hα(Hα) = α(Hα(Hα)).

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: ρν' = ββν' = α(ββ)ν' = ... . The term α 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)) α

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. A shorter variation replaces its two leading subterms with just SSI, since Hα(Hα) = SHHα = SSIHα.

This becomes much shorter with the use of the B,C,W combinators, as the equivalent

Yα = S(KU)(SB(KU))α = U(BαU) = BU(CBU)α = SSI(CBU)α

And with a pseudo-Haskell syntax it becomes the exceptionally short Y = U . (. U).

Following this approach, other fixpoint combinator definitions are possible. Thus,

* This Y, by Haskell Curry:

Hgx = g(xx) ; Yg = Hg(Hg) ; Y = S(KU)(SB(KU)) = SS(S(S(KS)K))(K(SII))

* Turing's Θ:

Hhg = g(hhg) ; Θg = HHg ; Θ = U(B(SI)U) = SII(S(K(SI))(SII))

* Y' (with SK-encoding by John Tromp[3]):

Hgh = g(hgh) ; Y'g = HgH ; Y' = WC(SB(C(WC))) = SSK(S(K(SS(S(SSK))))K)

* Θ4 by R. Statman:[4]

Hgyz = g(yyz) ; Θ4g = Hg(Hg)(Hg) ; Θ4 = B(WW)(BW(BBB))

* or in general,

Hsomething = g(hsomething) ; YHg = H_____H__g
(where anything goes instead of "_") or any other intermediary H combinator's definition, with its correspondent Y definition to jump-start it correctly. In particular,[5]
Labcdefghijklmnopqstuvwxyzr = r(thisisafixedpointcombinator) ; YK = LLLLLLLLLLLLLLLLLLLLLLLLLL

In a strict programming language the Y combinator will expand until stack overflow, or never halt in case of tail call optimization.[6] The Z combinator will work in strict languages (also called eager languages), where the applicative evaluation order is in effect.

Its difference from the Y combinator is that it is using Qx=B(Ux)I=S(K(Ux))I=S(BS(BKU))(KI)x as the η-expanded form of Y's plain Ux. Whereas Y = BU(CBU), we have Z = BU(CBQ) = S(KU)(SB(KQ)):

Z=λf.(λx.f(λv.xxv))(λx.f(λv.xxv))=λf.U(λx.f(λv.Uxv))=S(λf.U)(λf.λx.f(λv.Uxv))=S(KU)(λf.S(λx.f)(λx.λv.Uxv))=S(KU)(λf.S(Kf)(λx.λv.Uxv))=S(KU)(S(λf.S(Kf))(λf.λx.λv.Uxv))=S(KU)(S(S(λf.S)(λf.Kf))(K(λx.λv.Uxv)))=S(KU)(S(S(KS)K)(K(λx.λv.Uxv)))=S(KU)(S(S(KS)K)(K(λx.S(λv.Ux)(λv.v))))=S(KU)(S(S(KS)K)(K(λx.S(K(Ux))I)))=S(KU)(S(S(KS)K)(K(S(λx.S(K(Ux)))(λx.I))))=S(KU)(S(S(KS)K)(K(S(λx.S(K(Ux)))(KI))))=S(KU)(S(S(KS)K)(K(S(S(λx.S)(λx.K(Ux)))(KI))))=S(KU)(S(S(KS)K)(K(S(S(KS)(S(λx.K)(λx.Ux)))(KI))))=S(KU)(S(S(KS)K)(K(S(S(KS)(S(KK)U))(KI))))

### The reversal expression

S(K(SI))K reverses the two terms following it:

S(K(SI))Kαβ →
K(SI)α(Kα)β →
SI(Kα)β →
Iβ(Kαβ) →
Iβα →

It is thus equivalent to CI. And in general, S(K(Sf))K is equivalent to Cf, for any f.

### Boolean logic

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 = x

and

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 Booleans acting as 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:

NOT b = bFT = S(SI(KF))(KT)b

If this is put in an if-then-else structure, it has the expected result:

NOT(T) = (T)FT = F
NOT(F) = (F)FT = T

Boolean OR (which returns T if either of its two Boolean argument values is T) works the same as an if-then-else structure with T as the second value:

OR ab = aTb = SI(KT)ab

If this is put in an if-then-else structure, it has the expected result:

OR(T)(T) = (T)T(T) = T
OR(T)(F) = (T)T(F) = T
OR(F)(T) = (F)T(T) = T
OR(F)(F) = (F)T(F) = F

Boolean AND (which returns T if both its Boolean argument values are T) works the same as an if-then-else structure with F as the third value:

AND ab = abF = Sa(KF)b = SS(K(KF))ab

If this is put in an if-then-else structure, it has the expected result:

AND(T)(T) = (T)(T)F = T
AND(T)(F) = (T)(F)F = F
AND(F)(T) = (F)(T)F = F
AND(F)(F) = (F)(F)F = F

This proves that the SKI system can fully express Boolean logic.

SKI calculus is complete, so any other logical combinator can be expressed in it as well, like, e.g.,

XOR ab = OR (AND a (NOT b)) (AND (NOT a) b)

so that

XOR abxy = (a(bFT)F)T((aFT)bF)xy = a(byx)(bxy)

as well as

NOT bxy = bFTxy = b(Fxy)(Txy) = byx
OR abxy = aTbxy = a(Txy)(bxy) = ax(bxy)
AND abxy = abFxy = a(bxy)(Fxy) = a(bxy)y

## Connection to intuitionistic logic

The combinators K and S correspond to two well-known axioms of propositional logic:

AK: A → (B → A),
AS: (A → (B → C)) → ((A → B) → (A → C)).

Function application corresponds to the rule modus ponens:

MP: from A and A → B, infer B.

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:

* the implicational fragment of classical logic, would require the combinatory analog to the law of excluded middle, i.e., Peirce's law;
* full classical logic, would require the combinatory analog to the propositional axiom F → A.

This connection between the types of combinators and the corresponding logical axioms is an instance of the Curry–Howard isomorphism.

## Examples of reduction

There usually are multiple ways to do a reduction. If the term has a normal form, it is unique, hence the same result will be reached in such a case whatever the order of operations.

* SKI(KIS)
* SKI(KIS)⇒K(KIS)(I(KIS))⇒KI(I(KIS))⇒I
* SKI(KIS)⇒K(KIS)(I(KIS))⇒KIS⇒I
* SKI(KIS)⇒SKII⇒KI(II)⇒I
* KS(I(SKSI))
* KS(I(SKSI))⇒KS(I(KI(SI)))⇒KS(I(I))⇒KS(II)⇒KSI⇒S
* KS(I(SKSI))⇒S
* SKIK
* SKIK⇒KK(IK)⇒KKK⇒K
* SKIK⇒KK(IK)⇒K

## See also

* Combinatory logic
* B, C, K, W system
* Fixed point combinator
* Lambda calculus
* Functional programming
* Unlambda programming language
* The Iota and Jot programming languages, designed to be even simpler than SKI.
* To Mock a Mockingbird

## References

1. ↑ Schönfinkel, M. (1924). "Über die Bausteine der mathematischen Logik". Mathematische Annalen 92 (3–4): 305–316. doi:10.1007/BF01448013. Translated by Stefan Bauer-Mengelberg as van Heijenoort, Jean, ed (2002). "On the building blocks of mathematical logic". A Source Book in Mathematical Logic 1879–1931. Harvard University Press. pp. 355–366. ISBN 9780674324497. https://books.google.com/books?id=v4tBTBlU05sC&pg=PA355.
2. ↑ Curry, Haskell Brooks (1930). "Grundlagen der Kombinatorischen Logik" (in German). American Journal of Mathematics (Johns Hopkins University Press) 52 (3): 509–536. doi:10.2307/2370619.
3. ↑ https://tromp.github.io/
4. ↑ Larry Wos, William McCune (September 1988). Searching for Fixed Point Combinators by Using Automated Theorem Proving: A Preliminary Report. https://inis.iaea.org/collection/NCLCollectionStore/_Public/20/016/20016944.pdf. Retrieved December 12, 2024. , p.9
5. ↑ "A construction due to Klop 2007" https://ncatlab.org/nlab/show/fixed-point+combinator
6. ↑ Bene, Adam (17 August 2017). "Fixed-Point Combinators in JavaScript" (in en). Medium. https://blog.benestudio.co/fixed-point-combinators-in-javascript-c214c15ff2f6.

* Smullyan, Raymond (1985). To Mock a Mockingbird. Knopf. ISBN 0-394-53491-3. A gentle introduction to combinatory logic, presented as a series of recreational puzzles using bird watching metaphors.
* ~~~~ (1994). "Ch. 17–20". Diagonalization and Self-Reference. Oxford University Press. ISBN 9780198534501. OCLC 473553893. are a more formal introduction to combinatory logic, with a special emphasis on fixed point results.

## External links

* O'Donnell, Mike "The SKI Combinator Calculus as a Universal System."
* Keenan, David C. (2001) "To Dissect a Mockingbird."
* Rathman, Chris, "Combinator Birds."
* ""Drag 'n' Drop Combinators (Java Applet)."
* A Calculus of Mobile Processes, Part I (PostScript) (by Milner, Parrow, and Walker) shows a scheme for combinator graph reduction for the SKI calculus in pages 25–28.
* the Nock programming language may be seen as an assembly language based on SK combinator calculus in the same way that traditional assembly language is based on Turing machines. Nock instruction 2 (the "Nock operator") is the S combinator and Nock instruction 1 is the K combinator. The other primitive instructions in Nock (instructions 0,3,4,5, and the pseudo-instruction "implicit cons") are not necessary for universal computation, but make programming more convenient by providing facilities for dealing with binary tree data structures and arithmetic; Nock also provides 5 more instructions (6,7,8,9,10) that could have been built out of these primitives.

0.00

(0 votes)

Original source: https://en.wikipedia.org/wiki/SKI combinator calculus. Read more

|

Retrieved from "https://handwiki.org/wiki/index.php?title=SKI_combinator_calculus&oldid=4298717"