A Taste of the λ Calculus

20 July 2013

I've been having a brain-bending good time reading
An Introduction to Functional Programming Through Lambda Calculus.
Using examples from that book, this article will walk you through the
basics of λ calculus. We'll then look at the surprising,
counterintuitive way that the λ calculus lets us represent conditional
expressions and boolean operations — all with functions as the only
values. It was a very different way of thinking for me, and exciting
to learn. I hope it's exciting for you, too!

A Bit of History

As every aspiring greybeard knows, the λ calculus was invented by
Alonzo Church in
response to David Hilbert's 1928
Entscheidungsproblem.
The Entscheidungsproblem inspired another computational model which
you may have heard of, the
Turing Machine.

The λ calculus is one of the foundations of computer science. It's
perhaps most famous for serving as the basis of Lisp, invented (or
discovered, if you prefer to think of Lisp as being on par with the
theory of gravity or the theory of evolution) by
[John McCarthy](http://en.wikipedia.org/wiki/JohnMcCarthy(computer_scientist))
in 1958.

Indeed, by examining the λ calculus, you can see where Lisp derives
its beauty. The λ calculus had a lean syntax and dead-simple
semantics, the very definition of mathematical elegance, yet it's
capable of representing all computable functions.

Enough history! Tell Me About λ Expressions!

The λ calculus is all about manipulating λ expressions. Below is its
specification. If you don't know what something means, don't worry
about it at this point - this is just an overview and we'll dig into
it more.

<expression>::=<name>|<function>|<application><name>::=anysequenceofnon-blankcharacters<function>::=λ<name>.<body><body>::=<expression><application>::=(<functionexpression><argumentexpression>)<functionexpression>::=<expression><argumentexpression>::=<expression>;;Examples;;Namesxjoeyqueen-amidala;;Functions;;Notethatfunctionsalwayshaveoneandonlyoneparameterλx.xλy.y;;equivalenttoabove;we'll get into that moreλfirst.λsecond.first;;thebodyofafunctioncanitselfbeafunctionλfn.λarg.(fnarg);;Application(λx.xλx.x)((λfirst.λsecond.firstx)y)

There are two super-cool things about this specification. First, it
really boils down to four elements: names, functions, application, and
"expressions" which can be any of the above. That's awesome! Second,
function bodies and function application arguments can be any
expression at all, meaning that a) functions can take functions as
arguments and b) functions can return functions.

You can see how this is directly related to functional programming,
where you have first class functions and higher order functions.
This is interesting in itself as it gives you a glimpse of the
theoretical underpinnings of functional programming.

But it gets way, way cooler. By the end of this article you'll see how
conditions and boolean operations can be represented in terms of
functions and functions that operate on functions. In order to get
there, let's first look at how function application works. Then we'll
go over some basic but crucial functions.

Function Application

When you apply a function to an argument expression, you replace
all instances of name within the function's body with the
argument expression.

Keep in mind that we're talking about a mathematical system here,
not a programming language. This is pure symbol manipulation,
without any regard for how actual hardware will carry out the
replace operation mentioned above.

Let's start to flesh out this purely abstract notion of function
application with some examples, starting with the identity function:

;;Identityfunctionλx.x

As you would expect, applying this function to an argument expression
returns the argument expression. In the example below, don't worry about
where "foo" comes from:

Make sense? Excellent! This will let us break your brain with greater
efficiency. Now pay attention, because things are about to get super
flippin' fantastic.

Argument Selection and Argument Pairing Functions

In the λ calculus, functions by definition have one and only one
parameter, the name. This might seem limiting, but it turns out that
you can build functions which allow you to work on multiple arguments.

The following functions together allow you to select either the first
or the second of two arguments. We'll look at them all together first
and then dig in to see how they work together.

select_first and select_second do what their names suggest,
selecting either the first or second of two arguments. They have the
same underlying structure; they're both functions which take a first
argument and evaluate to a function. This function is applied to a
second argument. select_first returns first, and select_second
returns second.

Let's see how this works with select_first:

;;Starthere((select_firstidentity)apply);;Substitutethefunctionitselffor"select_first"((λfirst.λsecond.firstidentity)apply);;Performthefirstfunctionapplication,replacing"first"with"identity".;;Thisreturnsanotherfunction,whichwe'll apply to a second argument.;;Noticethatthebodyoftheresultingfunctionis"identity",and;;thename"second"doesn't appear in the body at all(λsecond.identityapply);;Applyfunction.Since"second"doesn't appear in the function body,;;itdisappearsintotheether.identity

So, select_first and select_second are able to operate on a pair
of arguments.

But how do we create pairs for the to work on? make_pair creates a
"pair" by returning a function which expects either select_first or
select_second as its argument. This is awesome - we don't need any
data structures to represent a pair, all we need are functions!

So, to reiterate, make_pair works by taking a first argument. This
returns a function with takes a second argument. The result is a
function which you can apply to either select_first or
select_second to get the argument you want.

This is super freaking cool! A pair is a function which has "captured"
two arguments and which you then apply to a selection function.
Starting with just four basic constructs – names, functions,
applications, expressions – and five simple rules for performing
function application, we've been able to construct pairs of arguments
and select between them.

And things are about to get even more fun! We're now ready to see how
we can create conditional expressions and boolean operations purely
using λ expressions.

Conditional Expressions and Boolean Operations

The upcoming treatment of conditional expressions and boolean
operations is going to look kinda weird at first. You'll want to keep
in mind that in abstract math, elements don't have any inherent
meaning but are defined by the way with they interact with each other
— by their behavior.

For our purposes, the behavior of a conditional expression is to
select between one of two expressions, as shown by the following
pseudocode:

iftrue<expression>else<expression>end

Hmm... selecting between two expressions... we just went over that!
make_pair gave us a pair of expressions to choose between using
either select_first or select_second.

Because these functions result in the exact same behavior as if/else,
let's gon ahead repurpose these:

You're probably not used to thinking of a conditional expression as a
function which you apply to either true or false, but it works!

NOT, AND, OR

NOT can be seen as

ifxfalseelsetrueend

So, if x is true then false is selected, and if x is false
then true is selected. Let's look at this using the cond
expressions above:

;;Ingeneral(((cond<e1>)<e2>)true)=><e1>(((cond<e1>)<e2>)false)=><e2>;;ForNOT(((condfalse)true)true)=>false(((condfalse)true)false)=>true;;Soingeneral,wecansay:defnot=λbool.(((condfalse)true)bool);;Wecansimplifythis,thoughI'm lazy and won't show how:defnot=λbool.((boolfalse)true)

AND can be seen as

ifxyelsefalseend

In other words, if x is true then the value of the expression is the
value of y, otherwise it's false. Here's how we can represent that:

I won't work this one out - I'll leave it "as an exercise for the
reader." :)

The End

I hope you've enjoyed this brief taste of the λ calculus! We've only
scratched the surface of the kinds of neat things it's capable of. If
you thought this article was fun, then I definitely recommend
An Introduction to Functional Programming Through Lambda Calculus.
This fun tome provided most or all of the examples I've used, though
I've tried to present them in a way that's easier to understand. I
also recommend
The Art of Lisp & Writing,
which conveys the beauty and joy of coding in Lisp.