Home/Blog/Programming/Lambda calculus 101
Home/Blog/Programming/Lambda calculus 101

Lambda calculus 101

33 min read
Jan 22, 2024
content
Lambda expressions
Examples: Function abstraction
Binding
Associativity
Scope
Functions on more variables
How to do computations using lambda expressions
Alpha conversion
Representing boolean values
A conditional statement
Church numerals
Repetition
Arithmetic
Predecessor
Examples of encoded pairs
Relational operators
Greater than or equal to
Equality
Strict inequality

Become a Software Engineer in Months, Not Years

From your first line of code, to your first day on the job — Educative has you covered. Join 2M+ developers learning in-demand programming skills.

For anyone coming from a background of working with imperative programming languages, the experience of programming using a functional programming language can feel surreal. With no recourse to familiar looping constructs like for and while loops, with the glaring absence of assignment statements and familiar data structures, the learning curve consists of not just learning new syntax but also learning a new way of thinking. While the precise syntax and the details depend on the particular functional language being learned, the crucial underlying way of thinking is common across the board for all functional languages. This way of thinking is essentially the lambda calculus.

Lambda calculus was invented by Alonzo Church in the 1930s, who, as part of his inquiry into the foundational questions in mathematics, explored the idea of representing computation using functions. In fact, lambda calculus is equivalent to Turing machinesAn abstract model of computation invented by Alan Turing, who later went on to become Church’s PhD student. in the sense that everything that can be computed can also be expressed through lambda calculus, and everything that can be expressed through lambda calculus can be computed.

This blog discusses some of the essential elements in lambda calculus, and highlights how the symbolic expressions in lambda calculus can be used for computing. In particular, we show how such expressions can be used for implementing data types, as well as for representing arithmetic, logical, and relational operators.


If you’d like to learn more about functional programming, this course is a really good starting point.


Lambda expressions#

The fundamental building blocks of lambda calculus are known as lambda expressions. A lambda expression can be one of the following three things:

  1. A variable: A variable is just a name. For example, xx, yy, and zz can serve as variables.

  2. A function abstraction: A function abstraction is a function that has no name and takes in a single parameter. The general form of a function abstraction is

    λx.B\lambda x. B

    Here, the symbol λ\lambda means the start of an anonymous function. The name that follows λ\lambda is the parameter. The dot symbol (.)( . ) indicates that the function body is about to begin. The function body BB consists of a lambda expression.

    For convenience, we occasionally use the term lambda function to mean a function abstraction.

  3. A function application: A function application consists of any two lambda expressions, E1E_1 and E2E_2, placed side by side. In a function application such as

    (E1E2)(E_1E_2)

    , the expression E2E_2 can be thought of as an argument passed to E1E_1.

And that’s all the machinery at our disposal. Lambda calculus essentially consists of function abstractions and applying them in meaningful ways to other lambda expressions. While that may not sound like much, it’s amazing what can be accomplished using just these definitions.

Examples: Function abstraction#

  • λx. x\lambda x.\ x is the identity function. It returns whatever it receives.

  • λx. y\lambda x.\ y is an abstraction that takes a lambda expression xx, ignores it completely, and returns some other lambda expression yy. For this reason, it can be thought of as the constant function.

  • λy. yy\lambda y.\ y y is an abstraction that takes a lambda expression and applies it to itself.

Convention on spaces: When variable names consist of a single letter, as in the context of this blog, they need not be separated by spaces.

Binding#

  • The variable that appears right after a λ\lambda is said to be bound in the body of that abstraction. All other variables are said to be free.

    • In λx. x\lambda x.\ x, the variable xx is bound.
    • In λx. y\lambda x.\ y, the variable xx is bound but the variable yy is free.
    • In λy. zy\lambda y.\ zy, the variable yy is bound, but the variable zz is free.

Associativity#

  • Function application is left-associative. If there are three expressions listed as E1E2E3E_1E_2E_3, then

E1E2E3 means (E1E2)E3E_1E_2E_3 \quad \text{ means } \quad(E_1E_2)E_3

Scope#

  • The body or the scope of a lambda function goes as far to the right as possible. For example, the underbraces show the scope of λy\lambda y and λx\lambda x in the following lambda expression:

(λy. y(yy))(λx.xz) (\lambda y.\ \underbrace{y (yy )}) (\lambda x. \underbrace{xz})

Convention on parenthesization: Do note that the parentheses are part of the syntax to help with the grouping of the function applications. They can’t always be omitted without loss of meaning, but we do make some exceptions to avoid clutter:

  • We omit the outermost parentheses around a standalone lambda expression.
  • We omit the parenthesis surrounding two variables.
  • We omit parentheses that enforce associativity.

Functions on more variables#

Unlike mathematical functions, each lambda function has only one associated variable bound to its scope. To express the idea of a function on more than one variable, the lambda functions can be nested. For example, the function λx. λy. xy\lambda x.\ \lambda y.\ xy can be seen as a function that takes a parameter xx and returns a function of yy that applies xx to yy. This is easier to see with parentheses in place:

(λx. (λy. xy))(\lambda x.\ (\lambda y.\ xy))

The following visualization of a lambda expression may help internalize this idea:

A nesting of lambda expressions
A nesting of lambda expressions

Often, a lambda expression such as λx. λy. λz. E\lambda x.\ \lambda y.\ \lambda z.\ E is written in shorthand by gathering all variables against a single λ\lambda as shown:

λxyz. E\lambda xyz.\ E

How to do computations using lambda expressions#

To do computations with lambda expressions we need to be able to evaluate them, or reduce them to simpler forms. The primary way to express the evaluation of a function is through a rule called β\beta-reduction.

A function application like (λx.E1)E2(\lambda x. E_1)E_2 β\beta-reduces to an expression E1E_1' if E1E_1' is obtained by replacing all occurrences of the bound variable xx in E1E_1 by E2E_2. This is denoted

(λx.E1)E2    βE1(\lambda x. E_1)E_2 \stackrel{\beta}{\implies} E_1'

In applying β\beta-reductions, the leftmost λx.\lambda x. is removed, and the bound variable xx in the body of the abstraction is substituted by the expression E2E_2.

This represents the idea of passing E2E_2 as an argument to an actual function call and returning the result.

Let’s look at some examples of function applications that are beta reducible:

  • Example 1: The identity function applied to a free variable reduces to that free variable (since the identity function returns its argument.)

(λx.x)y    βy\begin{align*} &(\lambda x. x)y \\ \stackrel{\beta}{\implies} & y \end{align*}

  • Example 2: The identity function applied to itself reduces to the identity function.

    (λx.x)(λx.x)    βλx.x\begin{align*} &(\lambda x. x)(\lambda x. x) \\ \stackrel{\beta}{\implies} & \lambda x. x \end{align*}

  • Example 3: A function that applies its argument to itself, when applied to the identity function β\beta-reduces to the identity function.

    (λy. yy)(λx. x)    β(λx. x)(λx. x)λy is removed and y is substituted by λx.x    βλx. x    by Example 1\begin{align*} & \qquad (\lambda y.\ y y)(\lambda x.\ x)\\ \stackrel{\beta}{\implies} &\qquad (\lambda x.\ x)(\lambda x.\ x) \qquad \lambda y\text{ is removed and }y\text{ is substituted by }\lambda x. x \\ \stackrel{\beta}{\implies} & \qquad \lambda x.\ x \qquad \qquad \ \ \ \quad \text{ by Example 1} \end{align*}

  • Example 4: We can keep applying β\beta-reductions to the following expression, but the computation does not terminate.

(λy. yy)(λy. yy)    β(λy. yy)(λy. yy)    β(λy. yy)(λy. yy) \begin{align*} & \qquad (\lambda y.\ yy) (\lambda y.\ yy)\\ \stackrel{\beta}{\implies} &\qquad (\lambda y.\ yy) (\lambda y.\ yy)\\ \stackrel{\beta}{\implies} &\qquad (\lambda y.\ yy) (\lambda y.\ yy)\\ & \qquad \qquad \qquad \vdots \\ \end{align*}

  • Example 5: We illustrate the application of a nested function abstraction through the following set of slides.
Application of the leftmost function abstraction to a is beta reducible
1 / 4
Application of the leftmost function abstraction to a is beta reducible

Notation: We use the notation     β\stackrel{\beta^*}{\implies} to express zero or more beta reductions.

For example,

((λx. x)(λy. yy))(λw. w)    βλw. w((\lambda x.\ x) (\lambda y.\ yy))(\lambda w.\ w) \stackrel{\beta^*}{\implies} \lambda w.\ w

Note that when a lambda expression can’t be β\beta-reduced any further, it’s said to be in normal form. A non-terminating function application that keeps reducing to itself has no normal form. If the normal form of a lambda expression exists, then reducing the leftmost, outermost lambda application is guaranteed to lead to a unique normal form. This reduction strategy is called a normal order reduction.

Alpha conversion#

It doesn’t matter what name we give to a bound variable in a function as long as we do so consistently for all occurrences of the variable in the scope of that abstraction. So λx. x\lambda x.\ x is the same abstraction as λy. y\lambda y.\ y.

Now, consider the following expression. The variable yy within the lambda abstraction is a bound variable, but the variable yy to which the abstraction applies is a free variable. They’re not the same.

(λxy. xy)y(\lambda xy.\ x y) y

After a β\beta-reduction is attempted, the variable passed in as the argument becomes a bound variable.

(λxy. xy)y    βattemptedλy. yy\begin{align*} & (\lambda xy.\ x y) y \\ \stackrel{\stackrel{\text{attempted}}{\beta}}{\implies}& \lambda y.\ y y \end{align*}

This is called capturing a free variable. Notice how this capturing of yy inadvertently changed the meaning of the lambda expression to a self-application—which is other than what was intended.

To avoid capturing free variables, a bound variable can be carefully renamed across the entire scope of that abstraction. Such a renaming is called an α\alpha-conversion.

For the lambda expression from above, if we rename the bound variable yy as zz, we get:

(λxy. xy)y    α(λxz. xz)y    βλz. yz\begin{align*} & (\lambda x\red{y}.\ x \red{y}) y \\ \stackrel{\alpha}{\implies}& (\lambda x\red{z}.\ x \red{z}) y \\ \stackrel{\beta}{\implies}& \lambda z.\ y z \end{align*}

This now says what was intended: Apply the free variable yy to the argument of the lambda function.

Note: We used     α\stackrel{\alpha}{\implies} to explicitly indicate an alpha conversion, but these are usually done implicitly, as needed before applying β\beta-reductions.

But where’s the data?

We’re thinking about doing computations. But all we have are functions (abstracted functions, to be precise) and variables. So where’s the data? Well, when all we have is a lambda expression, the lambda expression is the data.

Before going into exactly what that means, we adopt a small notational convenience to keep ourselves from drowning in a frenzy of symbols:

Notation: We give a name to an abstraction. When naming an abstraction, we use the word define before the name given to it.

Let’s begin by defining two boolean values

Representing boolean values#

We name the following two function abstractions as True\text{True} and False\text{False}.

define True=λx. λy. x\text{True} = \lambda x.\ \lambda y.\ x

define False=λx. λy. y\text{False} = \lambda x.\ \lambda y.\ y

If we look at the semantics of these abstractions, True\text{True} takes two parameters and returns the first. Whereas False\text{False} takes two parameters and returns the second.

Note: We could have defined these differently, but it’s easier to go with how it’s typically done.

  • Let’s think about how to represent a not operator.

    defineNot=λa. a False True=λa. a (λxy. y) (λxy. x)\begin{align*} \textbf{define}\quad \text{Not} &= \lambda a.\ a \ \text{False}\ \text{True}\\ &= \lambda a.\ a \ (\lambda xy.\ y) \ (\lambda xy.\ x)\\ \end{align*}

    Applying Not\text{Not} to True\text{True} returns False\text{False} as shown below.

    Not True   (λa. a (λxy. y) (λxy. x)) (λtu. t)    β(λtu. t) (λxy. y) (λxy. x)    β(λu. (λxy. y)) (λxy. x)    βλxy. y   False\begin{align*} & \text{Not}\ \text{True}\\ \equiv\ \ \ & (\lambda a.\ a \ (\lambda xy.\ y) \ (\lambda xy.\ x))\ (\lambda tu.\ t)\\ \stackrel{\beta}{\implies} & (\lambda tu.\ t)\ (\lambda xy.\ y) \ (\lambda xy.\ x) \\ \stackrel{\beta}{\implies} & (\lambda u.\ (\lambda xy.\ y)) \ (\lambda xy.\ x) \\ \stackrel{\beta}{\implies}& \lambda xy.\ y \\ \equiv\ \ \ & \text{False} \end{align*}

    Similarly, applying Not\text{Not} to False\text{False} returns True\text{True}.

  • Now, let’s think about how to represent an and operator.

    defineAnd=λab. a b False=λab. a b (λxy.y)\begin{align*} \textbf{define}\quad \text{And} &= \lambda a b.\ a\ b \ \text{False}\\ &= \lambda a b.\ a\ b \ (\lambda xy.y)\\ \end{align*}

    If And\text{And} is applied to True False\text{True} \ \text{False}, the expression reduces to False\text{False} as expected:

    And True False    (λab. a b (λxy. y)) (λrs. r) (λtu. u)    β (λrs. r) (λtu. u) (λxy. y)    β λtu. u   False\begin{align*} &\text{And}\ \text{True}\ \text{False}\\ \equiv \ \ \ \ &(\lambda ab.\ a\ b\ (\lambda xy.\ y))\ (\lambda rs.\ r)\ (\lambda tu.\ u)\\ \stackrel{\beta^*}{\implies} \ & (\lambda rs.\ r)\ (\lambda tu.\ u)\ (\lambda xy.\ y) \\ \stackrel{\beta^*}{\implies} \ & \lambda tu.\ u\\ \equiv \ \ \ & \text{False} \end{align*}

    Similarly, when applied to any other combination of True\text{True} and False\text{False}, the outcome would be as expected:

    And True True    βTrueAnd False True    βFalseAnd True False    βFalse\begin{align*} &\text{And}\ \text{True}\ \text{True} \stackrel{\beta^*}{\implies} \text{True} \\ &\text{And}\ \text{False}\ \text{True} \stackrel{\beta^*}{\implies} \text{False}\\ &\text{And}\ \text{True}\ \text{False} \stackrel{\beta^*}{\implies} \text{False} \end{align*}

  • We can define Or\text{Or} using a similar idea:

    defineOr=λab. a True b=λab. a (λxy. x) b\begin{align*} \textbf{define}\quad \text{Or} &= \lambda a b.\ a\ \text{True}\ b \\ &= \lambda a b.\ a\ (\lambda xy.\ x)\ b\\ \end{align*}

    Just as before, the outcome of applying the Or\text{Or} operator can be verified to behave as expected:

    Or True True    βTrueOr True False    βTrueOr False True    βTrueOr False False    βFalse\begin{align*} &\text{Or}\ \text{True}\ \text{True} \stackrel{\beta^*}{\implies} \text{True}\\ &\text{Or}\ \text{True}\ \text{False} \stackrel{\beta^*}{\implies} \text{True}\\ &\text{Or}\ \text{False}\ \text{True} \stackrel{\beta^*}{\implies} \text{True}\\ &\text{Or}\ \text{False}\ \text{False}\stackrel{\beta^*}{\implies} \text{False} \end{align*}

A conditional statement#

The following expression is intended to serve as an if-else expression, where the branching depends on a condition cc:

define IfElse=λcxy. cxy\text{IfElse} = \lambda cxy.\ cxy

  • When applied to True\text{True} ff gg, the expression ff is returned:

    IfElse True f g   (λcxy. cxy) (λrs. r) f g    β(λxy. (λrs. r)xy) f g    β(λy. (λrs. r)fy) g    β(λrs. r)fg    β(λs. f)g    βf\begin{align*} & \red{\text{IfElse}}\ \text{True}\ f\ g\\ \equiv \ \ \ & \red{(\lambda cxy.\ c x y)}\ (\lambda r s.\ r)\ f\ g\\ \stackrel{\beta}{\implies}& \red{(\lambda xy.}\ (\lambda r s.\ r) \red{x y)}\ f\ g\\ \stackrel{\beta}{\implies}& \red{(\lambda y.}\ (\lambda r s.\ r) f \red{y)}\ g\\ \stackrel{\beta}{\implies}& (\lambda r s.\ r) f g\\ \stackrel{\beta}{\implies}& (\lambda s.\ f) g\\ \stackrel{\beta}{\implies}& f\\ \end{align*}

  • When applied to False f g\text{False}\ f\ g, the expression gg is returned:

    IfElse False f g   (λcxy. cxy) False f g    βg\begin{align*} & \text{IfElse}\ \text{False}\ f\ g\\ \equiv \ \ \ & (\lambda cxy.\ c x y)\ \text{False}\ f\ g\\ \stackrel{\beta^*}{\implies}& g \end{align*}

Church numerals#

We saw above how boolean values can be encoded as function abstractions. The set of non-negative integers can also be encoded similarly. First, a function is defined and is given the name 00. A successor function applied to 00 is called a 11. The successor is applied to 11 and is called a 22, and so on.

  • define 0=λsz. z0 = \lambda sz.\ z

    If zz is a variable representing zero and ss is a successor function, ignore ss and return zz.

  • define 1=λsz. sz1 = \lambda sz.\ sz

    Apply the successor function ss on zz.

  • define 2=λsz. s(sz)2 = \lambda sz.\ s(sz)

    First, apply the successor function ss on zz to get szsz, and then apply ss on szsz a second time.

  • define 3=λsz. s(s(sz))3 = \lambda sz.\ s(s(s z))

  • define n=λsz. s ((s(sn timesz)))n = \lambda sz.\ \underbrace{s\ (\ldots(s ( s }_{n\ \text{times}}z)))

Encodings defined this way are called Church numerals.

Terminology note: When referring to a lambda expression representation of a number nn as a Church numeral, we call it the numeral nn.

Repetition#

Suppose we want to apply a lambda abstraction ff repeatedly to another function gg a total of nn times, where nn is known in advance. How would we do this? Repetitions of this kind can be achieved through the application of the following abstraction:

define Repeat=λnxy. nxy\text{Repeat} = \lambda nxy.\ nxy

We use the variable name nn to help remember that the function expects the Church numeral nn.

If Repeat\text{Repeat} is applied to a Church numeral nn and the expressions ff and gg, we see that ff is applied to gg repeatedly:

Repeat (λsz. s(s(sn timesz))) f g   (λnxy. nxy) (λsz. s(s(sn timesz))) f g    β(λxy. (λsz. s(s(sn timesz))) x y) f g    β(λy. (λsz. s(s(sn timesz))) f y) g    β(λsz. s(s(sn timesz))) f g    β(λz. f( f(fn timesz))) g    βf(f(fn timesg)\begin{align*} &\text{Repeat} \ (\lambda sz.\ \underbrace{s(\ldots s(s}_{n\ \text{times}}z)))\ f\ g\\ \equiv \ \ \ &(\lambda nxy.\ nxy) \ (\lambda sz.\ \underbrace{s(\ldots s(s}_{n\ \text{times}}z)))\ f\ g\\ \stackrel{\beta}{\implies} &(\lambda xy.\ (\lambda sz.\ \underbrace{s(\ldots s(s}_{n\ \text{times}}z)))\ x\ y)\ f\ g \\ \stackrel{\beta}{\implies} &(\lambda y.\ (\lambda sz.\ \underbrace{s (\ldots s(s}_{n\ \text{times}}z)))\ f\ y)\ g \\ \stackrel{\beta}{\implies} &(\lambda sz.\ \underbrace{s(\ldots s(s}_{n\ \text{times}}z)))\ f\ g\\ \stackrel{\beta}{\implies} &(\lambda z.\ \underbrace{f(\ldots \ f(f}_{n\ \text{times}} z)))\ g\\ \stackrel{\beta}{\implies}& \underbrace{f(\ldots f ( f}_{n\ \text{times}} g) \end{align*}

Arithmetic#

How does one add two Church numerals? Let’s first encode something simpler: an increment of one.

  • define Inc=λnsz. s(nsz)\text{Inc} = \lambda nsz.\ s(nsz)

    The key idea is that the expression passed in ss is applied nn times to zz and then one more time again to get the Church numeral n+1n+1.

    Let’s apply Inc\text{Inc} on 22 to get 33:

    Inc 2   (λnsz. s(nsz)) (λtx. t(tx))    β(λsz. s((λtx. t(tx))sz)    β(λsz. s((λx. s(sx))z)λt is reduced next as it’s the leftmost function application    β(λsz. s(s(sz)))λx is reduced next since it’s the leftmost lambda application   3\begin{align*} &\text{Inc}\ \red{2}\\ \equiv \ \ \ & (\lambda nsz.\ s(nsz)) \ \red{(\lambda tx.\ t(tx))}\\ \stackrel{\beta}{\implies}& (\lambda sz.\ s(\red{(\lambda tx.\ t(tx))}sz) \\ \stackrel{\beta}{\implies}& (\lambda sz.\ s( \red{(\lambda x.}\ s(s\red{x})\red{)}z) \qquad \text{$\lambda t$ is reduced next as it's the leftmost function application}\\ \stackrel{\beta}{\implies}& (\lambda sz.\ s( s(sz))) \qquad \text{$\lambda x$ is reduced next since it's the leftmost lambda application}\\ \equiv \ \ \ & 3 \end{align*}

  • To represent adding two numbers nn and mm, we apply Inc\text{Inc} on the Church numeral mm a total of nn times:

    define Add=λnm.n Inc m\text{Add} = \lambda n m. n\ \text{Inc}\ m

    When applied to a Church numeral nn, the numeral takes Inc\text{Inc} as the successor function and mm as the expression on which Inc\text{Inc} is to be applied nn times. So in fact, such an application would reduce to the following:

    λm. Inc (Inc (Incn times m))\lambda m.\ \underbrace{\text{Inc}\ ( \ldots \text{Inc}\ (\text{Inc}}_{n\ \text{times}}\ m))

    Let’s see this in action applied to 22 and 22:

Add 2 2   (λnm. n Inc m) 2 2     β2 Inc 2     β Inc (Inc 2)     β (λabc. b(abc))  (Inc 2)     β λbc. b((Inc 2) bc)     β λbc. b(3 b c)     β λbc. b(b(b(bc)))   4  \begin{align*} &\text{Add} \ 2\ 2\\ \equiv \ \ \ &(\lambda nm.\ n\ \text{Inc}\ m) \ 2\ 2\ \\ \stackrel{\beta^*}{\implies}&2\ \text{Inc}\ 2\ \\ \stackrel{\beta^*}{\implies}&\ \text{Inc}\ (\text{Inc}\ 2)\ \\ \stackrel{\beta^*}{\implies}&\ (\lambda abc.\ b(abc))\ \ (\text{Inc}\ 2)\ \\ \stackrel{\beta^*}{\implies}&\ \lambda bc.\ b((\text{Inc}\ 2)\ bc) \ \\ \stackrel{\beta^*}{\implies}&\ \lambda bc.\ b(3\ b\ c) \ \\ \stackrel{\beta^*}{\implies}&\ \lambda bc.\ b(b(b(bc)))\\ \equiv\ \ \ & 4 \ \\ \end{align*}

If you’d like to get practice verifying this in symbolic form by hand, we include the reductions here for assistance:

  • To represent the multiplication of two numbers nn and mm, we want to add mm a total number of nn times to 00.

    define Mult=λnm. n (Add m) 0\text{Mult} = \lambda n m.\ n\ (\text{Add}\ m)\ 0

    Essentially, when applied to a Church numeral nn, this is equivalent to:

    λm. (Add m)((Add m) ((Add  m)n times 0)) \lambda m.\ \underbrace{\gray{(}\text{Add}\ m\gray{)}(\ldots \gray{(}\text{Add}\ m\gray{)}\ (\gray{(}\text{Add}\ \ m\gray{)}}_{n\ \text{times}}\ 0))

    For example, when Mult\text{Mult} is applied to 33 and 22, it is equivalent to:

    Mult 3 2(Add 2) ((Add 2) ((Add 2) 0))\text{Mult}\ 3\ 2 \equiv \gray{(}\text{Add}\ 2\gray{)}\ (\gray{(}\underbrace{\text{Add}\ 2\gray{)}\ \underbrace{(\gray{(}\text{Add}\ 2\gray{)}\ 0)}})

Note: The names given to the abstraction must be replaced by the abstractions before applying beta reductions.

Predecessor#

Previously, we defined Inc\text{Inc} to get the successor of a numeral. It’s not so easy how we would get a predecessor of a numeral nn. Consider a numeral such as

n=λsz. s(s(sn timesz))n = \lambda sz.\ \underbrace{s(\ldots s(s}_{n\ \text{times}}z))

How do we strip an ss to get the following?

n1=λsz. s(s(sn1 timesz))n-1 = \lambda sz.\ \underbrace{s(\ldots s(s}_{n-1\ \text{times}}z))

Intuition: The way around this is to incrementally construct pairs of numbers (k,k1)(k, k-1) where kk increases in steps of one, and then once we have the pair (n,n1)(n, n-1), the second element of the pair is returned to get the predecessor of nn.

define Pair=λxyb. bxy\text{Pair} = \lambda xyb.\ bxy

The role of the third variable bb envisioned here is to return one of the two elements in the (x,y)(x, y) pair. This can be seen by passing True\text{True} or False\text{False} as an argument to bb:

  • The expression (Pair 1 0 True\text{Pair}\ 1\ 0\ \text{True}) returns 11.
  • The expression (Pair 1 0 False\text{Pair} \ 1\ 0\ \text{False}) returns 00.

Examples of encoded pairs#

Using the definition of Pair\text{Pair}, we can encode pairs of consecutive integers.

  • So for example, the (0,0)(0,0) pair is codified as the following expression:

    define (0,0)=(λxyb. bxy) 0 0(0,0) = (\lambda xyb.\ bxy)\ 0\ 0     βλb. b 0 0\stackrel{\beta}{\implies} \lambda b.\ b\ 0\ 0

  • Similarly, we can encode other pairs as lambda expressions:

    define (1,0)=(λxyb. bxy) 1 0(1,0) = (\lambda xyb.\ bxy)\ 1\ 0     βλb. b 1 0\stackrel{\beta}{\implies} \lambda b.\ b\ 1\ 0

    define (2,1)=(λxyb. bxy) 2 1(2,1) = (\lambda xyb.\ bxy)\ 2\ 1     βλb. b 2 1\stackrel{\beta}{\implies} \lambda b.\ b\ 2\ 1

Given a pair represented by pp, we can get the successive pair by using the Inc\text{Inc} abstraction (defined earlier for incrementing):

define NextPair=λp. Pair (Inc (p True))1stargument (p True)2ndargument\text{NextPair} = \lambda p.\ \text{Pair}\ \underbrace{(\text{Inc}\ (p\ \text{True}))}_{ 1^{st} \text{argument} } \ \underbrace{(p\ \text{True})}_{2^{nd} \text{argument}}

Here, the pair pp applied to True\text{True} selects the first element in the pair pp. This selected element is used twice in order to construct the next pair. For example, the abstraction NextPair\text{NextPair} applied to (0,0)(0,0) gives us the encoding for (1,0)(1,0):

NextPair (λb. b 0 0)(0,0)    βλb. b 1 0\begin{align*} &\text{NextPair}\ \underbrace{(\lambda b.\ b\ 0\ 0)}_{(0, 0)}\\ \stackrel{\beta^*}{\implies}& \lambda b.\ b\ 1\ 0 \end{align*}

We’d like to apply the NextPair abstraction nn times starting with the pair (0,0)(0,0).

Note: In starting with (0,0)(0, 0), we assume (for convenience) that the predecessor of 00 is 00.

We apply the abstraction NextPair\text{NextPair} nn times as follows:

λn. n NextPair (λb. b 0 0) (n,n1)\begin{align*} &\lambda n.\ n\ \text{NextPair}\ (\lambda b.\ b\ 0\ 0)\\ \equiv \ &(n, n-1) \end{align*}

Finally, to select the second element in a pair, we apply that pair to False\text{False}:

define Second=λp. p False\text{Second} = \lambda p.\ p\ \text{False}

For example, let’s apply Second\text{Second} on (2,1)(2,1) to see how this works:

(λp. p False)((λnmb. bnm) 2 1)    β(((λnmb. bnm) 2 1) False)    β(((λmb. b 2 m) 1) False)    β((λb. b 2 1) False)    β(λxy. y) 2 1    β1\begin{align*} &(\lambda p.\ p\ \text{False}) ((\lambda nmb.\ bnm) \ 2 \ 1)\\ \stackrel{\beta}{\implies}& (((\lambda nmb.\ bnm) \ 2 \ 1)\ \text{False})\\ \stackrel{\beta}{\implies}& (((\lambda mb.\ b\ 2\ m) \ 1)\ \text{False})\\ \stackrel{\beta}{\implies}& ((\lambda b.\ b\ 2\ 1)\ \text{False})\\ \stackrel{\beta}{\implies}& (\lambda xy.\ y)\ 2\ 1 \\ \stackrel{\beta}{\implies}& 1 \end{align*}

We can finally use Second\text{Second} to return the predecessor of a numeral nn by selecting the second element of the pair (n,n1)(n, n-1):

define Pred=(λn. Second n NextPair (λb. b 0 0))\text{Pred} = (\lambda n.\ \text{Second} \ n\ \text{NextPair}\ (\lambda b.\ b\ 0\ 0))

Relational operators#

Finally, we look at how relational operators such as ==, \leq, \geq, <<, >> (for expressing equality and inequality) are expressed in lambda calculus.

For example, to check if mnm \leq n, we can perform the subtraction mnm-n and see if the outcome is a 00. Testing for 00 suffices since we defined the predecessor of 00 to be 00.

We define the abstraction TestZero\text{TestZero} as follows to check for equality of the input with 00. Later, we’ll use this abstraction to define other operators.

define TestZero=λn. n (λw. False) True\text{TestZero} = \lambda n.\ n\ (\lambda w.\ \text{False})\ \text{True}

Recall that the numeral 00 is defined as λsz. z\lambda sz.\ z, which is exactly the definition of False\text{False}. So when TestZero\text{TestZero} is applied to 00, it returns True\text{True} as shown below:

(λn. n (λw. False) True) 0(λn. n (λwrt. t) (λxy. x))(λsz. z)    β(λsz.z) (λwrt. t) (λxy. x)    β(λz. z)(λxy. x)    β(λxy. x)\begin{align*} &(\lambda n.\ n\ (\lambda w.\ \text{False})\ \text{True}) \ 0\\ \equiv \quad & (\lambda n.\ n\ (\lambda wrt.\ t)\ (\lambda xy.\ x)) (\lambda sz.\ z)\\ \stackrel{\beta}{\implies}& (\lambda sz. z)\ (\lambda wrt.\ t)\ (\lambda xy.\ x) \\ \stackrel{\beta}{\implies}& (\lambda z.\ z) (\lambda xy.\ x) \\ \stackrel{\beta}{\implies}& (\lambda xy.\ x) \\ \end{align*}

When it is applied to a numeral other than 00, it returns False\text{False}. Let’s see this for the numeral 22.

(λn. n (λw. False) True) 2(λn. n (λwrt. t) (λxy. x)) (λsz. s(sz))    β(λsz. s(sz)) (λwrt. t) (λxy. x)    β(λz. (λwrt. t)((λwrt. t)z)) (λxy. x)    β(λwrt. t)((λwrt. t)(λxy. x))    β(λrt. t)\begin{align*} &(\lambda n.\ n\ (\lambda w.\ \text{False})\ \text{True}) \ 2\\ \equiv \quad & (\lambda n.\ n\ (\lambda wrt.\ t)\ (\lambda xy.\ x)) \ (\lambda sz.\ s(sz))\\ \stackrel{\beta}{\implies}& (\lambda sz.\ s(sz))\ (\lambda wrt.\ t)\ (\lambda xy.\ x) \\ \stackrel{\beta}{\implies}& (\lambda z.\ (\lambda wrt.\ t)((\lambda wrt.\ t)z))\ (\lambda xy.\ x) \\ \stackrel{\beta}{\implies}& (\lambda wrt.\ t)((\lambda wrt.\ t)(\lambda xy.\ x)) \\ \stackrel{\beta}{\implies}& (\lambda rt.\ t) \\ \end{align*}

Note that the effect of applying TestZero\text{TestZero} to a numeral other than 00 means that (λw. False)(\lambda w.\ \text{False}) is repeatedly applied to True\text{True}. The repeated applications always return False\text{False}.

Greater than or equal to#

To implement mnm \geq n, we need to test nmn-m and see whether it’s 00. The subtraction nmn-m is performed by using the predecessor expression Pred\text{Pred} on the numeral nn repeatedly (mm times):

define GE=λmn. TestZero (m Pred n)\text{GE} = \lambda m n.\ \text{TestZero}\ (m\ \text{Pred} \ n)

If mm is strictly less than nn, then nmn-m is not 00. Otherwise it is 00.

Similarly, we can construct a lambda expression, say LE\text{LE}, to implement the less than or equal to operator ()(\leq).

Equality#

For any two numbers nn and mm, if nmn \geq m and nmn \leq m, it implies that n=mn = m. So we can check for equality between two numerals through the lambda expression:

define Equal=λnm. And (GE nm)(LE nm)\text{Equal} = \lambda nm.\ \text{And}\ (\text{GE}\ nm) (\text{LE}\ nm)

Similarly, to check that nmn \neq m, we can use the lambda expression:

define NE=λnm. Not (Equal nm)\text{NE} = \lambda nm.\ \text{Not}\ (\text{Equal}\ nm)

Strict inequality#

Checking for strict inequality can be done using other operators. For example, we can check if nn is strictly greater than mm through the following lambda expression:

define GT=λnm. And (GE nm) (NE nm)\text{GT} = \lambda nm.\ \text{And}\ (\text{GE}\ nm)\ (\text{NE}\ nm)

We can check for strictly less than in a similar way.

define LT=λnm. And (LE nm) (NE nm)\text{LT} = \lambda nm.\ \text{And}\ (\text{LE}\ nm)\ (\text{NE}\ nm)

Let’s recap what we covered:

  • We defined lambda expressions to represent boolean values and non-negative integers.
  • We saw how to encode the idea of an if-else expression and the idea of repetition.
  • We figured out how to build logical operators and arithmetic operators from first principles.

We hope that this was fun for you and that it continues to be useful to you as you learn and grow!

Frequently Asked Questions

What is lambda calculus used for?

Lambda calculus, or λ-calculus for short, is like a language in mathematical logic. It’s all about explaining how computations work, using function abstraction and application, and playing around with variables and substitutions. You can think of it as a one-size-fits-all model for computation—it can mimic any Turing machine.


Written By:
Mehvish Poshni
Join 2.5 million developers at
Explore the catalog

Free Resources