• the concept of λ-definissability was the first of what is accepted now like the exact equivalent of mathematical descriptions for which algorithms exist. Stephen Kleene in Origins off Recursive Function Theory , Year off the history off Computing, 1081, flight; 3, n°1, page 52

The lambda-calculation (or λ-calculation ) is a theoretical Computer programming language invented by Alonzo Church in the Années 1930. It makes it possible to the mathematicians to work in particular on the concepts of function and application. It also finds many applications in the field of the proof.

This language was the first used to define and to characterize the functions recursive S. It has a great importance in the theory of the Calculabilité, with equal of the machines of Turing and the Modèle of Herbrand-Gödel.

It is about a model of calculation, i.e. a formalization of the concept of calculation. One can simulate the standardization λ-terms using a machine of Turing, and simulate a machine of Turing by λ-terms. These two models are thus equivalent (or Turing-equivalents). The Thèse of Church-Turing affirms that any algorithm can be calculated by lambda-calculation, therefore by the equivalence which any algorithm can be calculated by a machine of Turing. A program being an algorithm allowing to solve a given problem, we have the first computer programming language there.

The lambda typified calculations are at the origin of the functional computer programming languages like Lisp, Caml or Haskell: the theory of the standard brings a strong and sure semantics to them.

Lambda-calculation is related with the Combinatory logic due to Haskell Curry.

Syntax

Formally, the lambda calculation is defined by three kinds of lambda-terms:

  • variable : x, y,…
  • the applications: uv is a lambda-term if u and v are lambda-terms.
  • the abstractions: \ lambda x.u is a lambda-term if x is a variable and u a lambda-term.

The application can be seen with u like function, and v like argument. uv is then the image of v by the function u.
The abstraction can also be interpreted so that: \ lambda x.u is the function u (X)

For practical reasons, one sometimes also introduces primitive constants and operations (for example natural entireties and operations + and * usual). All these primitives will hide lambda terms (cf below). Thus, the function which takes a parameter and adds 2 to him f (X) = X + 2 will be defined like \ lambda x.x+2. The " reduction finale" (standardization) of \ lambda X. (x+2) 3 will be thus 5.

Notations

Multiple arguments/Abstraction

You will have noticed that a lambda takes one argument but one can circumvent this obstacle in the following way: the function which with the couple ( X , there ) associates U will be regarded as a function which, with X , associates a function which, with there , associates U . It is thus noted: λx. (λy.u) . From the point of view of the notation one can also write λx.λy.u or λxλy.u or quite simply λxy.u. For example, the function which, with the couple ( X , there ) associates X + there could be noted \ lambda X. \ lambda y.x+y or more simply \ lambda xy.x+y.

Parenthesizing

From the point of view of parenthesizing x1 x2… xn = ((x1 x2)… xn)

For parenthesizing, there are several formalisms:

  1. \ Lambda \ to Var | \ lambda VAr \ Lambda | \Lambda \Lambda | (\ Lambda)

As explained above, a parenthesizing on the left. (One will agree to use this convention here)
  1. \ Lambda \ to Var | \ lambda VAr \ Lambda | \ Lambda (\ Lambda)
Who corresponds to a parenthesizing on the right.

Contraction

One can contract certain parts of a lambda-term: several successive applications of the same variable term/can “be factorized”.

\ lambda X. F F F F x is equivalent to: \ lambda X. f^ {4} x

Free variables

In the mathematical expressions there are two types of variables: free variables and dependant variables (or dumb women). The dependant variables are local with a piece of the expression. One can at will re-elect them without changing the significance of the expression.

For example in f (X) = \ int_a^b {X + y~dy} , X is free, but is dependant there (by Dy). This is the same expression as f (X) = \ int_a^b {X + z~dz} because was a local name there, just like is Z. On the other hand f (X) = \ int_a^b {Z + y~dy} does not correspond to the same expression because X is free. Moreover the notation F (X) specifies that the whole of its variables (free) is {X}.

Just like the integral binds the variable of integration, the λ binds the variable which follows it.

Examples: in \ lambda x.xy X is dependant, and there free. One can rewrite this term as follows: \lambda t.ty.

More complicated \ lambda xyzt. Z (xt) ab (zsy) is also equivalent to \ lambda wjit. I (wt) ab (isj)

One defines the unit VL ( T ) of the free variable of a term T by recurrence:

  • if x is a variable then VL (X) = {X}
  • if u and v are lambda-terms then VL (U v) = VL (U) ∪ VL (v)
  • if x is a variable and u a lambda-term then VL (λx.u) = VL (U) - {X}

If a lambda-term then does not have free variables one says that it is closed .

Principles

The most important tool for calculation is the substitution . This operation makes it possible to replace a variable by a term. Thanks to this mechanism, the reductions will be able “to calculate” the term (NB: it is what one does in manner machinale in traditional algebraic handling, whose lambda calculation is, precisely, a generalization and an abstraction).

The substitution in a lambda term T of a variable X by a term U is noted T: = U. It is said that one substitutes U for X in T, and one defines it by recurrence on T:

  • if T is a variable then T: = u=u if x=t and T if not
  • if T = v W then T: = U = v: = U W: = U
  • if T = λy.v then T: = U = λy. (v: = U) if x \ neqy and T if not
Note: in the last case one will pay attention so that there U is not a free variable. Indeed, it “would then be captured” by the external lambda. If it is the case one re-elects there and all its occurrences in v. It is the alpha-conversion, which makes it possible to identify \ lambday.v and \ lambdaz.v: = Z. Recall that the variables under the lambdas are variables known as “dumb”.

Examples:

  • \lambdax.xy := = \ lambdax.xa
  • \ lambdax.xy has: = X = \ lambdaz.zx (and not \ lambda x.xx, which is completely different, cf notices above)

Note:: the alpha-conversion, which makes it possible to identify \ lambday.v and \ lambdaz.v: = Z must be defined with precaution and before substitution. Indeed the substitution of Z in v is not yet available and poses the same problems of capture. Thus in the term \ lambdax. \ lambday.xy \ lambdaz.z, one will not be able to re-elect X in there (one would obtain \ lambday. \ lambday.yy \ lambdaz.z) on the other hand one can re-elect X in Z.

The formal and correct definitions of alpha-conversion and substitution are in the book of J. - L. Krivine for example, but a good comprehension of the concept of associated, usual variables in mathematics, is largely enough to include/understand lambda-calculation.

Defined thus substitution is an external mechanism with lambda-calculation, one also says that it belongs to the méta-theory. It should be noted that certain work aims at introducing substitution like an internal mechanism with lambda-calculation, leading to what is called calculations of explicit substitutions .

Reductions

The terms are trees with binary nodes (applications), unary nodes (λ-abstractions) and sheets (variables). The reductions make it possible to modify the tree, however the tree is not inevitably “smaller” after the operation. Example: if you reduce (λx.xxx) (λx.xxx) you will obtain (λx.xxx) (λx.xxx) (λx.xxx). The reduction holds its name owing to the fact that one will reduce a piece of the term: the rédex .

One calls rédex a term of the form ( \ lambdax.u) v. One defines the beta-contraction then (or reduction ) of ( \ lambdax.u) v like U: = v; one notes → the relation of reduction: thus ( \ lambdax.u) v give U: = v. Intuitively, ( \ lambdax.u) v are the value of the function \ lambdax.u applied to the variable v . U being the image of X by the function ( \ lambdax.u), the image of v is obtained in substituent in U , X by v . Conversely U: = v are called the contractum ( \ lambdax.u) v.

Example of reduction :

( \ lambdax.xy) has gives xy: = = ay has

One notes →* the transitive reflexive closing of the relation → of contraction (→* is called the beta-reduction ) and =β his symmetrical and transitive reflexive closing (called beta-conversion or beta-equivalence ).

Β-conversion makes it possible to make a " step back " starting from a term. That allows, for example, to find the term before a β-reduction. To pass from x to (\ lambda y.y) x.

One can write M =_ {\ beta} Me \ mbox {if} \ exists N_ {1},…, N_ {p} \ mbox {such as} M = N_ {1}, M'=N_ {p} \ mbox {and} N_ {I} \ to N_ {i+1} \ mbox {or} N_ {i+1} \ to N_ {I}

One thus will travel by reductions, and flashbacks of M with Me.

One also rather frequently uses another operation, called eta-reduction (or its reverse the eta-expansion ), defined as follows: \ lambdax.ux →η U, when X does not appear free in U. Indeed, ux interprête like the image of X by the function U . \ lambdax.ux interprête then like the function which, with X , associates the image of X by U , therefore like the function U itself.

Lastly, if primitives were given, one can fix their calculative behavior by means of the rules of delta-reduction . For example, if one gave oneself the entireties and + like additional terms, the tables of addition will be used as delta-rules. As the primitives are by definition completely foreign with lambda-calculation, their rules of calculation can adopt a priori any form. However, if one wants to extend the properties mentioned below to the case of a calculation with primitives, one is brought to make some assumptions on the added rules.

Standardization: concept of calculation

The calculation of a lambda-term can be defined by its reduction. Its original form is the statement and the normal form is the result.

A lambda-term T is known as in normal form if no beta-contraction can be to him applied, i.e. if T does not contain any rédex. Or, if there does not exist any lambda-term U such as T → U.

Example: \ lambda x.y (\ lambda z.z (yz))

It is said that a lambda-term T is normalisable if there exists a normal form U and a succession of t1 lambda-terms,   …,   tn such as T = t1, U = tn and for any I (1 ≤ I < N) ti → ti+1 or ti+1 → ti. Such a U is called the normal form of T.

It is said that a lambda term T is strongly normalisable if all the reductions starting from T are finished .

Examples:

  1. Here the most traditional example of lambda-term not strongly normalisable:
    \ Omega = (\ lambda x.xx) (\ lambda x.xx) = \ Delta \ Delta
    the lambda term \ Omega is not strongly normalisable because it buckles infinitely on itself if one tries to reduce it.
    (\ lambda x.xx) (\ lambda x.xx) \ to (\ lambda x.xx) (\ lambda x.xx) . ( (\ lambda x.xx) applies to the first argument of (\ lambda x.xx) , both x according to the point thus take this value, and one finds the starting term.)
  2. (\ lambda x.x) ((\ lambda y.y) Z) is strongly a lambda-term normalisable.
  3. (\ lambda x.y) (\ Delta \ Delta) is normalisable, but not strongly normalisable.

If a term is strongly normalisable, then it is normalisable.

Theorem of Church-Rosser: is T and U two terms such as T =β U. There exists a term v such as T →* v and U →* v.

Theorem of the rhombus (or of junction): is T, u1 and u2 lambda-terms such as T →* u1 and T →* u2. Then there exists a lambda-term v such as u1* v and u2* v.

Thanks to the Théorème of Church-Rosser one can easily show the unicity of the normal form as well as the coherence of the lambda-calculation (i.e. there exist at least two nonbeta-convertible distinct terms).

Various Lambda-calculations

Lambda-calculation defined above is only one syntactic screen called to be supplemented by a semantics which makes it possible to give a significance so that one makes by this lambda-calculation. One can distinguish two big classes from lambda-calculations: not typified lambda-calculations and typified lambda-calculations. The difference is that with the types one will restrict the valid terms . I.e. generally one will seek to remove the terms which are not normalisables. The goal is to have a lambda-calculation which vérfie properties of Church-Rosser and standardization (even strong standardization).

Even if by restricting the valid terms by the types one generally keeps most interesting. Moreover thanks to the Isomorphisme of Curry-Howard one can connect a lambda calculation to a logic and by there a term corresponds to a proof. Intuitively it is very strong because that makes it possible to associate with a mathematical proof a computer program. It is on that part of the automatic and semi-automatic prouveurs is based.

Lambda-calculation not typified

One will use codings to create the usual objects of data processing. Thanks to these objects one can all calculate because one can simulate a machine of Turing. And thanks to the theorems of the Theory of the calculability one from of deduced that lambda-calculation is a universal model of calculation.

The Boolean ones

In the Syntax part, we saw that it is practical to define primitives. It is what we will do here.

true will be \ lambda ab.a
false will be \ lambda ab.b

This is only the definition of a coding, and one could define others of them.

We notice that:

true X there →* X
and that:
false X there →* there

We can thus deduce a lambda-term representing from it the alternative: yew-then-else . It is a function with three argument, which takes Boolean and two lambda terms, which turns over the first if the Boolean one is true and the second if not.

\lambda buv.buv

Our function is well checked:

ifthenelse vrai x y = \ lambda buv.buv vrai x y
ifthenelse vrai x y \ lambda UV. (vrai u v) x y
ifthenelse vrai x y* vrai x y
ifthenelse vrai x y* \ lambda xy.x x y
ifthenelse vrai x y* X

One will see same manner as

ifthenelse false X there →* there

From there we have also a lambda-term for the traditional Boolean operations:

not = λb.ifthenelse B false true
and = λab.ifthenelse has B false (or λab.ifthenelse has B a)
or = λab.ifthenelse has true B (or λab.ifthenelse has b)

Entireties

We will use the Entiers of Church . N = λfx.f (F (… (F X)…)) = λfx.fnx with N F. For example 0 = λfx.x, 3 = λfx.f (F (F X)).

Some functions

There are two manners of coding the function successor. Either by adding a F at the head or in tail. At the beginning we have N = λfx.fn X and we want λfx.fn+1 X. It is necessary to be able to add a F either at the beginning of F “under” the lambdas or for the end.

  • If we choose to put it at the head, it is necessary to be able to enter “under” the lambdas. For that it is necessary to thus create redex if N is our entirety it is necessary to make N F X, which gives fn X. By putting a F at the head we almost finished: F (N F X) → F (fn X) = fn+1 X. We should now remake the lambdas to rebuild an entirety of Church: λfx.f (N F X) = λfx.fn+1 X (we could of course have taken other names of variables that F and X at the preceding stage and thus we would have kept these names here). Finally to have the “function” successor it is of course necessary to take an entirety in parameter, therefore to add a lambda. We obtain λnfx.f (N F X). The reader will be able to check that (λnfx.f (N F X)) 3 = 4, with 3 = λfx.f (F (F X))) and 4 = λfx.f (F (F (F X)))).

  • Si on the other hand we want to put F in tail, it is slightly more crafty one. When we “peeled” the entirety to remove the lambdas, we applied simple variables to our lambdas. Nothing prevents us from putting another thing. However here we want well F X in the place of X: we thus will do this: N F (F X), which is reduced to fn (F X) = fn+1 X. One does not have any more that to remake packing as in the preceding case and one obtains λnfx.n F (F X). The same checking could be made.

The other functions are built with the same principle, by applying what it is necessary in F and X. For example addition in “concaténant” the two lambda-terms: λnpfx.n F (p F X).

To code the multiplication it is a little smarter: one will use F “to propagate” a function on all the term: λnpfx. (N (p F) X)

The exponential one is not commonplace as opposed to what its writing lets think, and during the reduction one is obliged to re-elect the variables: λnp.p N

The predecessor and the subtraction are not simple either. One will speak again about it further.

One can as follows define the test in 0: if0thenelse = λnab.n (λx.b) has, or by using Boolean the λn.n (λx.faux) true.

Itérateurs

All that is not very intuitive then to be able to code algorithms one defines the function of iteration on the entireties: iterate = λnuv.n U v

The trick it is that v are the basic case and U a function on the case of recurrence.

For example the addition which comes from these equations

  • add (0, p) = p
  • add (n+1, p) = (n+p) + 1

One thus will program by iteration with the successor on the basic case p. the lambda-term is thus λnp.iterate N successor p. One will notice that add N p →* N successor p.

Couples

One can code couples by C = λz.z has B where has is the first element and B the second. One will note this couple (has, b). To reach the two parts one uses π1 = λc.c (λab.a) and π2 = λc.c (λab.b). One will recognize Boolean the true and false in these expressions and one will leave the care to the reader to reduce π1 (λz.z has b)

Lists

One can notice that an entirety is a list which does not contain a key . By adding information one can build the lists in a way similar to the entireties: ; …; an = λgy. G a1 (… (G an there)…)

Itérateurs on the lists

Same manner that one introduced an iteration on the entireties one introduces an iteration on the lists. the function liste_it is defined by λlxm.l X m as for the entireties. The concept is about the same one but there are small nuances. We will see for an example.

example: The length of a list is defined by

  • length () = 0
  • length (X:: L) = 1 + length L
X:: L is the list of head X and tail L (notation ml). That is coded by λl.liste_it L (λym.add (λfx.f X) m) (λfx.x) or quite simply λl.l (λym.add 1 m) 0

Binary trees

The principle of construction of the entireties, the couples and the lists spreads with the binary trees:

  • manufacturer of sheet: break into leaf = λngy.y N
  • manufacturer of node: node = λbcgy.g (B G there) (C G there) (with B and C of the binary trees)
  • iterator: arbre_it = λaxm.a X m
A tree is either a sheet, or a node. In this model, no information is stored on the level of the nodes, the data (or keys) are preserved at the level of the sheets only. One can then define the function which calculates the number of sheets of a tree: nb_feuilles = λa.arbre_it has (λbc.add B c) (λn.1), or more simply: nb_feuilles = λa.a add (λn.1)

The predecessor

To define the predecessor with the entireties of Church, one needs ruser and to use the couples. The idea is to rebuild the predecessor by iteration: pred = λn.π1 (iterate N (λc. (π2 C, successor (π2 c))) (0,0)). One notes while passing that the predecessor on the natural entireties is not especially defined into 0. One arbitrarily adopted here the convention which it would be worth 0.

It is checked for example that pred 3 →* π1 (iterate 3 (λc. (π2 C, successor (π2 c))) (0,0)) →* π1 (2,3) →* 2.

One from of deduced that the subtraction is definable by: sub = λnp.iterate p pred N with convention that if p is larger than N, then sub N p is worth 0.

Recursion

By combining predecessor and iterator, one obtains a recursor . This one is distinguished from the iterator by the fact that the function which passed in argument has access to the predecessor.

rec = λnfx.π1 (N (λc. (F (π2 c) (π1 c), successor (π2 c))) (X, 0))

The fixed controller of point

The fixed controller of point makes it possible to make infinite loops. This is practical to program functions which are not expressed simply by iteration, such as the pgcd, and it is especially necessary to program partial functions.

The controller of point of fixed simplest is: Y = λf. (λx.f (X X))(λx.f (X X))

It is checked that Y \ G \ =_ \ beta \ G (Y \ G) some is G. Thanks to the controller of point of fixed, one can for example define a function which takes in argument a function and tests if this function argument returns true for at least an entirety: teste_annulation = λg.Y (λfn.ou (G N) (F (successor N))) 0.

For example, if one defines the alternate continuation of Boolean the true and false : alternate = nonfalse λn.iterate N, then, it is checked that: teste_annulation alternate →* or (alternate 0) (Y (λfn.ou (alternate N) (F successor N)) (successor 0)) →* or (alternate 1) (Y (λfn.ou (alternate N) (F successor N)) (successor 1)) →* true.

One can also define the pgcd: pgcd = Y (λfnp.if0thenelse (sub p N) (if0thenelse (sub N p) p (F p (sub N p))) (F N (sub p N))).

Connection with the recursive partial functions

The recursor and the point fix are key ingredients allowing to show that all recursive partial Fonction is definable in λ-calculation when the entireties are interpreted by the entireties of Church. Reciprocally, the λ-terms can be coded by entireties and the reduction of the λ-terms is definable like a function (partial) recursive. Λ-calculation is thus a model of the Calculabilité

Simply typified Lambda-calculation

One introduces simple types on the terms, and one accepts only the well typified terms . In addition to a preoccupation of clearness and a comprehension, that makes it possible to have the strong standardization , i.e. for all the terms, all the reductions lead to a normal form (which is single for each starting term). In other words, all the calculations carried out in this context finish. N the other hand, the expressive capacity of this calculation is very limited (thus, the exponential one cannot be defined there, nor even the function n \ rightarrow 2^n).

More formally, the types are built in the following way:

  • a basic type \ iota (if there are primitives, one can also give itself several types of bases, like the entireties, the Boolean ones, the characters, etc but that does not have an incidence on the level of the theory).
  • if \ tau_1 and \ tau_2 is types, \ tau_1 \ rightarrow \ tau_2 is a type.

Intuitively, the second case represents the type of the functions accepting an element of the type \ tau_1 and returning an element of the type \ tau_2.

A context \ Gamma is a whole of pairs of the form (X, \ tau) where x is a variable and \ tau a type. A judgment of typing is a triplet \ Gamma \ vdash t:\tau (it is said whereas t is well typified in \ Gamma), recursively defined by:

  • if (X, \ tau) \ in \ Gamma, then \ Gamma \ vdash x:\tau .
  • if \ Gamma \ cup (X, \ tau_1) \ vdash u:\tau _2, then \ Gamma \ vdash \ lambda X \!: \! \ tau_1.u \: \, \ tau_1 \ rightarrow \ tau_2.
  • if \ Gamma \ vdash u:\tau _1 \ rightarrow \ tau_2 and \ Gamma \ vdash v:\tau _1, then \ Gamma \ vdash U v:\tau _2

If primitives were given, a type should be given them (via \ Gamma). In the case of the rule of the abstraction, the addition of x mask a possible preceding occurrence of the variable in \ Gamma.

Typified Lambda-calculations of higher natures

Simply typified lambda-calculation is too restrictive to be able to calculate all the functions which one usually needs in mathematics and thus in a computer program. Another model makes it possible to calculate all the calculable functions in a more or less acceptable time: the primitive recursion. This system makes it possible to calculate the majority of the calculable functions by a machine of Turing. The problem is on the level of the complexity because there are not goods algorithms of calculation and thus efficiently to calculate. This problem is regulated with the Système T of Gödel which amalgamates primitive recursion and simply typified lambda-calculation. In this system there are not only new algorithms but also of new programs as famous the Fonction of Ackermann which is the smallest recursive nonprimitive function. However it is out of reach current computers because there would not even be enough memory to store the result of the function applied to the arguments 5 5 for example.

Although this model makes it possible to calculate all that one wants with correct algorithms, theoretically one can much better do. In particular by the introduction of the variables of the type. That increases the complexity (from the comprehension point of view and nondata-processing) of the system but increases the expressive capacity considerably. For the lambdas calculations typified of the second order one can make terms which depend on types, of the types which depend on terms and of the types which depend on types. By making the combination of each one one obtains eight lambda-calculations that Barendregt models in the form of cube. The end of the cube opposed to that of simply typified lambda-calculation is the Calcul of constructions due to Thierry Coquand, and gave rise to the system cock.

Random links:2004 in architecture | Mónica Cruz | Barby (the Ardennes) | Diddl | Leigh Julius