User Contributed Dictionary
Noun
combinators- Plural of combinator
Extensive Definition
Combinatory logic is a notation introduced by
Moses
Schönfinkel and Haskell
Curry to eliminate the need for variables in mathematical
logic. It has more recently been used in computer science as a
theoretical model of computation and also as a basis for the design
of
functional programming languages. It is based on combinators. A
combinator is a higher-order
function which, for defining a result from its arguments,
solely uses function application and earlier defined
combinators.
Combinatory logic in mathematics
Combinatory logic was originally intended as a 'pre-logic' that would clarify the role of quantified variables in logic, essentially by eliminating them. Another way of eliminating quantified variables is Quine's predicate functor logic. While the expressive power of combinatory logic typically exceeds that of first-order logic, the expressive power of predicate functor logic is identical to that of first order logic (Quine 1960, 1966, 1976).The original inventor of combinatory logic,
Moses
Schönfinkel, published nothing on combinatory logic after his
original 1924
paper, and largely ceased to publish after Stalin consolidated
his power in 1929. Curry rediscovered the combinators while working
as an instructor at the University
of Princeton in the late 1927. In the latter
1930s,
Alonzo
Church and his students at Princeton
University invented a rival formalism for functional
abstraction, the lambda
calculus, which proved more popular than combinatory logic. The
upshot of these historical contingencies was that until theoretical
computer science began taking an interest in combinatory logic in
the 1960s and
70s, nearly all
work on the subject was by Haskell
Curry and his students, or by Robert Feys
in Belgium.
Curry and Feys (1958), and Curry et al. (1972) survey the early
history of combinatory logic. For a more modern parallel treatment
of combinatory logic and the lambda calculus, see Barendregt
(1984), who also reviews the models
Dana
Scott devised for combinatory logic in the 1960s and 70s.
Combinatory logic in computing
In computer science, combinatory logic is used as a simplified model of computation, used in computability theory and proof theory. Despite its simplicity, combinatory logic captures many essential features of computation.Combinatory logic can be viewed as a variant of
the lambda
calculus, in which lambda expressions (representing functional
abstraction) are replaced by a limited set of combinators,
primitive functions from which free
variables are absent. It is easy to transform lambda
expressions into combinator expressions, and combinator reduction
is much simpler than lambda reduction. Hence combinatory logic has
been used to model some
non-strict functional
programming languages and hardware.
The purest form of this view is the programming language Unlambda, whose
sole primitives are the S and K combinators augmented with
character input/output. Although not a practical programming
language, Unlambda is of some theoretical interest.
Combinatory logic can be given a variety of
interpretations. Many early papers by Curry showed how to translate
axiom sets for conventional logic into combinatory logic equations
(Hindley and Meredith 1990). Dana Scott in the 1960s and 70s showed
how to marry model theory
and combinatory logic.
Summary of the lambda calculus
The lambda calculus is concerned with objects
called lambda-terms, which are strings of symbols of one of the
following forms:
- v
- λv.E1
- (E1 E2)
where v is a variable name drawn from a
predefined infinite set of variable names, and E1 and E2 are
lambda-terms. Terms of the form λv.E1 are called abstractions. The
variable v is called the formal
parameter of the abstraction, and E1 is the body of the
abstraction.
The term λv.E1 represents the function which,
applied to an argument, binds the formal parameter v to the
argument and then computes the resulting value of E1---that is, it
returns E1, with every occurrence of v replaced by the
argument.
Terms of the form (E1 E2) are called
applications. Applications model function invocation or execution:
the function represented by E1 is to be invoked, with E2 as its
argument, and the result is computed. If E1 (sometimes called the
applicand) is an abstraction, the term may be reduced: E2, the
argument, may be substituted into the body of E1 in place of the
formal parameter of E1, and the result is a new lambda term which
is equivalent to the old one. If a lambda term contains no subterms
of the form (λv.E1 E2) then it cannot be reduced, and is said to be
in normal
form.
The expression E[v := a] represents the result of
taking the term E and replacing all free occurrences of v with a.
Thus we write
- (λv.E a) => E[v := a]
By convention, we take (a b c d ... z) as short
for (...(((a b) c) d) ... z). (i.e., application is left
associative.)
The motivation for this definition of reduction
is that it captures the essential behavior of all mathematical
functions. For example, consider the function that computes the
square of a number. We might write
- The square of x is x*x
(Using "*" to indicate multiplication.) x here is
the formal
parameter of the function. To evaluate the square for a
particular argument, say 3, we insert it into the definition in
place of the formal parameter:
- The square of 3 is 3*3
To evaluate the resulting expression 3*3, we
would have to resort to our knowledge of multiplication and the
number 3. Since any computation is simply a composition of the
evaluation of suitable functions on suitable primitive arguments,
this simple substitution principle suffices to capture the
essential mechanism of computation. Moreover, in the lambda
calculus, notions such as '3' and '*' can be represented without
any need for externally defined primitive operators or constants.
It is possible to identify terms in the lambda calculus, which,
when suitably interpreted, behave like the number 3 and like the
multiplication operator.
The lambda calculus is known to be
computationally equivalent in power to many other plausible models
for computation (including Turing
machines); that is, any calculation that can be accomplished in
any of these other models can be expressed in the lambda calculus,
and vice versa. According to the Church-Turing
thesis, both models can express any possible computation.
It is perhaps surprising that lambda-calculus can
represent any conceivable computation using only the simple notions
of function abstraction and application based on simple textual
substitution of terms for variables. But even more remarkable is
that abstraction is not even required. Combinatory logic is a model
of computation equivalent to the lambda calculus, but without
abstraction. The advantage of this is that evaluating expressions
in lambda calculus is quite complicated because the semantics of
substitution must be specified with great care to avoid variable
capture problems. In contrast, evaluating expressions in
combinatory logic is much simpler, because there is no notion of
substitution.
Combinatory calculi
Since abstraction is the only way to manufacture
functions in the lambda calculus, something must replace it in the
combinatory calculus. Instead of abstraction, combinatory calculus
provides a limited set of primitive functions out of which other
functions may be built.
Combinatory terms
A combinatory term has one of the following
forms:
- x
- P
- (E1 E2)
Reduction in combinatory logic
In combinatory logic, each primitive combinator
comes with a reduction rule of the form
- (P x1 ... xn) = E
where E is a term mentioning only variables from
the set x1 ... xn. It is in this way that primitive combinators
behave as functions.
Examples of combinators
The simplest example of a combinator is I, the
identity combinator, defined by
- (I x) = x
for all terms x. Another simple combinator is K,
which manufactures constant functions: (K x) is the function which,
for any argument, returns x, so we say
- ((K x) y) = x
for all terms x and y. Or, following the
convention for multiple application,
- (K x y) = x
A third combinator is S, which is a generalized
version of application:
- (S x y z) = (x z (y z))
S applies x to y after first substituting z into
each of them. Or put another way, x is applied to y inside the
environment z.
Given S and K, I itself is unnecessary, since it
can be built from the other two:
- ((S K K) x)
-
- = (S K K x)
- = (K x (K x))
- = x
- = (K x (K x))
- = (S K K x)
for any term x. Note that although ((S K K) x) =
(I x) for any x, (S K K) itself is not equal to I. We say the terms
are extensionally
equal. Extensional equality captures the mathematical notion of
the equality of functions: that two functions are equal if they
always produce the same results for the same arguments. In
contrast, the terms themselves, together with the reduction of
primitive combinators, capture the notion of intensional equality
of functions: that two functions are equal only if they have
identical implementations up to the expansion of primitive
combinators when these ones are applied to enough arguments. There
are many ways to implement an identity function; (S K K) and I are
among these ways. (S K S) is yet another. We will use the word
equivalent to indicate extensional equality, reserving equal for
identical combinatorial terms.
A more interesting combinator is the fixed
point combinator or Y combinator, which can be used to
implement recursion.
Completeness of the S-K basis
It is a perhaps astonishing fact that S and K can
be composed to produce combinators that are extensionally equal to
any lambda term, and therefore, by Church's thesis, to any
computable function whatsoever. The proof is to present a
transformation, T[ ], which converts an arbitrary lambda
term into an equivalent combinator.
T[ ] may be defined as follows:
- T[x] => x
- T[(E₁ E₂)] => (T[E₁] T[E₂])
- T[λx.E] => (K T[E]) (if x is not free in E)
- T[λx.x] => I
- T[λx.λy.E] => T[λx.T[λy.E]] (if x is free in E)
- T[λx.(E₁ E₂)] => (S T[λx.E₁] T[λx.E₂])
Conversion of a lambda term to an equivalent combinatorial term
For example, we will convert the lambda term
λx.λy.(y x) to a combinator:
- T[λx.λy.(y x)]
-
- = T[λx.T[λy.(y x)]] (by 5)
- = T[λx.(S T[λy.y] T[λy.x])] (by 6)
- = T[λx.(S I T[λy.x])] (by 4)
- = T[λx.(S I (K x))] (by 3 and 1)
- = (S T[λx.(S I)] T[λx.(K x)]) (by 6)
- = (S (K (S I)) T[λx.(K x)]) (by 3)
- = (S (K (S I)) (S T[λx.K] T[λx.x])) (by 6)
- = (S (K (S I)) (S (K K) T[λx.x])) (by 3)
- = (S (K (S I)) (S (K K) I)) (by 4)
- = T[λx.(S T[λy.y] T[λy.x])] (by 6)
- = T[λx.T[λy.(y x)]] (by 5)
If we apply this combinator to any two terms x
and y, it reduces as follows:
- (S (K (S I)) (S (K K) I) x y)
-
- = (K (S I) x (S (K K) I x) y)
- = (S I (S (K K) I x) y)
- = (I y (S (K K) I x y))
- = (y (S (K K) I x y))
- = (y (K K x (I x) y))
- = (y (K (I x) y))
- = (y (I x))
- = (y x)
- = (S I (S (K K) I x) y)
- = (K (S I) x (S (K K) I x) y)
The combinatory representation, (S (K (S I)) (S
(K K) I)) is much longer than the representation as a lambda term,
λx.λy.(y x). This is typical. In general, the T[ ]
construction may expand a lambda term of length n to a
combinatorial term of length Θ(3n).
Explanation of the T[ ] transformation
The T[ ] transformation is motivated by
a desire to eliminate abstraction. Two special cases, rules 3 and
4, are trivial: λx.x is clearly equivalent to I, and λx.E is
clearly equivalent to (K T[E]) if x does not appear free in
E.
The first two rules are also simple: Variables
convert to themselves, and applications, which are allowed in
combinatory terms, are converted to combinators simply by
converting the applicand and the argument to combinators.
It's rules 5 and 6 that are of interest. Rule 5
simply says that to convert a complex abstraction to a combinator,
we must first convert its body to a combinator, and then eliminate
the abstraction. Rule 6 actually eliminates the abstraction.
λx.(E₁ E₂) is a function which takes an argument,
say a, and substitutes it into the lambda term (E₁ E₂) in place of
x, yielding (E₁ E₂)[x : = a]. But substituting a into (E₁ E₂) in
place of x is just the same as substituting it into both E₁ and E₂,
so
(E₁ E₂)[x := a] = (E₁[x := a] E₂[x := a])
(λx.(E₁ E₂) a) = ((λx.E₁ a) (λx.E₂ a)) = (S λx.E₁
λx.E₂ a) = ((S λx.E₁ λx.E₂) a)
By extensional equality,
λx.(E₁ E2) = (S λx.E₁ λx.E₂)
Therefore, to find a combinator equivalent to
λx.(E₁ E₂), it is sufficient to find a combinator equivalent to (S
λx.E₁ λx.E₂), and
(S T[λx.E₁] T[λx.E₂])
evidently fits the bill. E₁ and E₂ each contain
strictly fewer applications than (E₁ E₂), so the recursion must
terminate in a lambda term with no applications at
all—either a variable, or a term of the form λx.E.
Simplifications of the transformation
η-reduction
The combinators generated by the T[ ]
transformation can be made smaller if we take into account the
η-reduction rule:
T[λx.(E x)] = T[E] (if x is not free in E)
λx.(E x) is the function which takes an argument,
x, and applies the function E to it; this is extensionally equal to
the function E itself. It is therefore sufficient to convert E to
combinatorial form.
Taking this simplification into account, the
example above becomes:
T[λx.λy.(y x)] = ... = (S (K (S I)) T[λx.(K x)])
= (S (K (S I)) K) (by η-reduction)
This combinator is equivalent to the earlier,
longer one:
(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)
Similarly, the original version of the
T[ ] transformation transformed the identity function
λf.λx.(f x) into (S (S (K S) (S (K K) I)) (K I)). With the
η-reduction rule, λf.λx.(f x) is transformed into I.
One-point basis
There are one-point bases from which every
combinator can be composed extensionally equal to any lambda term.
The simplest example of such a basis is where:
X ≡ λx.((xS)K)
It is not difficult to verify that: X (X (X X))
=ηβ K and X (X (X (X X)))) =ηβ S.
Since is a basis, it follows that is a basis
too.
Another simple example of a one-point basis
is:
X ≡ λx.(x K S K) with (X X) X =β K and X (X X) =β
S
X' does not need η contraction in order to
produce K and S. X is a one-point basis in CL+ext but not in CL
without extensionality. X' is a one-point basis in both CL and
CL+ext.
Combinators B, C
In addition to S and K, Schönfinkel's
paper included two combinators which are now called B and C, with
the following reductions:
(C a b c) = (a c b) (B a b c) = (a (b c))
He also explains how they in turn can be
expressed using only S and K.
These combinators are extremely useful when
translating predicate logic or lambda calculus into combinator
expressions. They were also used by Curry, and
much later by
David Turner, whose name has been associated with their
computational use. Using them, we can extend the rules for the
transformation as follows:
- T[x] => x
- T[(E₁ E₂)] => (T[E₁] T[E₂])
- T[λx.E] => (K T[E]) (if x is not free in E)
- T[λx.x] => I
- T[λx.λy.E] => T[λx.T[λy.E]] (if x is free in E)
- T[λx.(E₁ E₂)] => (S T[λx.E₁] T[λx.E₂]) (if x is free in both E₁ and E₂)
- T[λx.(E₁ E₂)] => (C T[λx.E₁] T[E₂]) (if x is free in E₁ but not E₂)
- T[λx.(E₁ E₂)] => (B T[E₁] T[λx.E₂]) (if x is free in E₂ but not E₁)
Using B and C combinators, the transformation of
λx.λy.(y x) looks like this:
T[λx.λy.(y x)] = T[λx.T[λy.(y x)]] = T[λx.(C
T[λy.y] x)] (by rule 7) = T[λx.(C I x)] = (C I) (η-reduction) =
C*(traditional canonical notation : X* = X I) = I(traditional
canonical notation: X = C X)
And indeed, (C I x y) does reduce to (y x):
(C I x y) = (I y x) = (y x)
The motivation here is that B and C are limited
versions of S. Whereas S takes a value and substitutes it into both
the applicand and its argument before performing the application, C
performs the substitution only in the applicand, and B only in the
argument.
The modern names for the combinators come from
Haskell
Curry's doctoral thesis of 1930 (see B,C,K,W
System). In Schönfinkel's
original paper, what we now call S, K, I, B and C were called S, C,
I, Z, and T respectively.
CLK versus CLI calculus
A distinction must be made between the CLK as described in this article and the CLI calculus. The distinction corresponds to that between the λK and the λI calculus. Unlike the λK calculus, the λI calculus restricts abstractions to:-
- λx.E where x has at least one free occurrence in E.
Reverse conversion
The conversion L[ ] from combinatorial
terms to lambda terms is trivial:
L[I] = λx.x L[K] = λx.λy.x L[C] = λx.λy.λz.(x z
y) L[B] = λx.λy.λz.(x (y z)) L[S] = λx.λy.λz.(x z (y z)) L[(E₁ E₂)]
= (L[E₁] L[E₂])
Note, however, that this transformation is not
the inverse transformation of any of the versions of T[ ]
that we have seen.
Undecidability of combinatorial calculus
A normal form is any combinatory term in which
the primitive combinators that occur, if any, are not applied to
enough arguments to be simplified. It is undecidable whether a
general combinatory term has a normal form; whether two combinatory
terms are equivalent, etc. This is equivalent to the undecidability
of the corresponding problems for lambda terms. However, a direct
proof is as follows:
First, observe that the term
Ω = (S I I (S I I))
has no normal form, because it reduces to itself
after three steps, as follows:
(S I I (S I I)) = (I (S I I) (I (S I I))) = (S I
I (I (S I I))) = (S I I (S I I))
and clearly no other reduction order can make the
expression shorter.
Now, suppose N were a combinator for detecting
normal forms, such that
(N x) => T, if x has a normal form F,
otherwise.
(Where T and F represent the conventional
Church
encodings of true and false, λx.λy.x and λx.λy.y, transformed
into combinatory logic. The combinatory versions have T = K and F =
(K I).)
Now let
Z = (C (C (B N (S I I)) Ω) I)
now consider the term (S I I Z). Does (S I I Z)
have a normal form? It does if and only if the following do
also:
(S I I Z) = (I Z (I Z)) = (Z (I Z)) = (Z Z) = (C
(C (B N (S I I)) Ω) I Z) (definition of Z) = (C (B N (S I I)) Ω Z
I) = (B N (S I I) Z Ω I) = (N (S I I Z) Ω I)
Now we need to apply N to (S I I Z). Either (S I
I Z) has a normal form, or it does not. If it does have a normal
form, then the foregoing reduces as follows:
(N (S I I Z) Ω I) = (K Ω I) (definition of N) =
Ω
but Ω does not have a normal form, so we have a
contradiction. But if (S I I Z) does not have a normal form, the
foregoing reduces as follows:
(N (S I I Z) Ω I) = (K I Ω I) (definition of N) =
(I I) I
which means that the normal form of (S I I Z) is
simply I, another contradiction. Therefore, the hypothetical
normal-form combinator N cannot exist.
The combinatory logic analogue of Rice's
theorem says that there is no complete nontrivial predicate. A
predicate is a combinator that, when applied, returns either T or
F. A predicate N is nontrivial if there are two arguments A and B
such that NA=T and NB=F. A combinator N is complete if and only if
NM has a normal form for every argument M. The analogue of Rice's
theorem then says that every complete predicate is trivial. The
proof of this theorem is rather simple.
Proof: By reductio ad absurdum. Suppose there is
a complete non trivial predicate, say N. Because N is supposed to
be non trivial there are combinators A and B such that (N A) = T
and (N B) = F.
Define NEGATION ≡ λx.(if (N x) then B else A) ≡
λx.((N x) B A) Define ABSURDUM ≡ (Y NEGATION)
Fixed point theorem gives: ABSURDUM = (NEGATION
ABSURDUM), for ABSURDUM ≡ (Y NEGATION) = (NEGATION (Y NEGATION)) ≡
(NEGATION ABSURDUM).
Because N is supposed to be complete either:
- (N ABSURDUM) = F or
- (N ABSURDUM) = T
Case 1: F = (N ABSURDUM) = N (NEGATION ABSURDUM)
= (N A) = T, a contradiction. Case 2: T = (N ABSURDUM) = N
(NEGATION ABSURDUM) = (N B) = F, again a contradiction.
Hence (N ABSURDUM) is neither T nor F, which
contradicts the presupposition that N would be a complete non
trivial predicate. QED.
From this undecidability theorem it immediately
follows that there is no complete predicate that can discriminate
between terms that have a normal form and terms that do not have a
normal form. It also follows that there is no complete predicate,
say EQUAL, such that: (EQUAL A B) = T if A = B and (EQUAL A B) = F
if A ≠ B. If EQUAL would exist, then for all A, λx.(EQUAL x A)
would have to be a complete non trivial predicate.
Applications
Compilation of functional languages
Functional programming languages are often based on the simple
but universal semantics of the lambda
calculus.
David Turner used his combinators to implement
the SASL
programming language.
Kenneth
E. Iverson used primitives based on Curry's combinators in his
J
programming language, a successor to APL.
This enabled what Iverson called tacit
programming, that is, programming in functional expressions
containing no variables, along with powerful tools for working with
such programs. It turns out that tacit programming is possible in a
clumsier manner in any APL-like language with user-defined
operators (Pure
Functions in APL and J).
Logic
The Curry-Howard isomorphism implies a connection between logic and programming: every proof of a theorem of intuitionistic logic corresponds to a reduction of a typed lambda term, and conversely. Moreover, theorems can be identified with function type signatures. Specifically, a typed combinatory logic corresponds to a Hilbert system in proof theory.The K and S combinators correspond to the axioms
- AK: A → (B → A),
- AS: (A → (B → C)) → ((A → B) → (A → C)),
- MP: from A and A → B infer B.
- X\Vdash A\iff A\in X.
Let A be any formula which is not provable in the
calculus. Then A does not belong to the deductive closure X of the
empty set, thus X\not\Vdash A, and A is not intuitionistically
valid.
See also
- SKI combinator calculus
- B,C,K,W system
- Fixed point combinator
- graph reduction machine
- supercombinators
- Lambda calculus and Cylindric algebra, other approaches to modelling quantification and eliminating variables
- To Mock a Mockingbird
- combinatory categorial grammar
- Categorical abstract machine
- Applicative computing systems
References
Further reading
- Hendrik Pieter Barendregt, 1984. The Lambda Calculus, Its Syntax and Semantics. Studies in Logic and the Foundations of Mathematics, Volume 103, North-Holland. ISBN 0-444-87508-5
- Field, Anthony J. and Peter G. Harrison, 1998. Functional Programming. . Addison-Wesley. ISBN 0-201-19249-7
- Hindley, Roger, and Meredith, 1990, "Principal Type-Schemes and Condensed Detachment," Journal of Symbolic Logic 55: 90-105
- Paulson, Lawrence C., 1995. Foundations of Functional Programming. University of Cambridge.
- Quine, W. V., 1960 "Variables explained away", Proceedings of the American Philosophical Society 104:3:343-347 (Jun. 15, 1960) at JSTOR. Reprinted as Chapter 23 of Quine's Selected Logic Papers (1966), pp. 227–235
- Moses Schönfinkel, 1924, "Über die Bausteine der mathematischen Logik," translated as "On the Building Blocks of Mathematical Logic" in From Frege to Gödel: a source book in mathematical logic, 1879-1931, Jean van Heijenoort, ed. Harvard University Press, 1967. ISBN 0-674-32449-8. The article that founded combinatory logic.
- Sørensen, Morten Heine B. and Paweł Urzyczyn, 1999. Lectures on the Curry-Howard Isomorphism. University of Copenhagen and University of Warsaw, 1999.
- 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. Diagonalization and Self-Reference. Oxford Univ. Press. Chpts. 17-20 are a more formal introduction to combinatory logic, with a special emphasis on fixed point results.
- Wolfengagen, V.E. Combinatory logic in programming. Computations with objects through examples and exercises. -- 2-nd ed. -- M.: "Center JurInfoR" Ltd., 2003. -- x+337 с. ISBN 5-89158-101-9.
External links
- Keenan, David C. (2001) "To Dissect a Mockingbird."
- Rathman, Chris, "Combinator Birds."
- ""Drag 'n' Drop Combinators (Java Applet)."
combinators in German: Kombinatorische
Logik
combinators in Spanish: Lógica
combinatoria
combinators in Croatian: Kombinatorna
logika
combinators in Russian: Комбинаторная
логика
combinators in French: Logique
combinatoire
combinators in Chinese: 组合子逻辑