This commit is contained in:
parent
d3d9145610
commit
482189f355
3 changed files with 153 additions and 8 deletions
|
@ -1,7 +0,0 @@
|
||||||
---
|
|
||||||
title: Lambda calculus part 1
|
|
||||||
date: 2024-09-10T10:16:12.486Z
|
|
||||||
type: default
|
|
||||||
draft: true
|
|
||||||
tags: [pl-theory]
|
|
||||||
---
|
|
152
src/content/posts/2024-09-10-lambda-calculus/index.md
Normal file
152
src/content/posts/2024-09-10-lambda-calculus/index.md
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
---
|
||||||
|
title: The lambda calculus
|
||||||
|
date: 2024-09-10T10:16:12.486Z
|
||||||
|
type: default
|
||||||
|
draft: true
|
||||||
|
tags: [pl-theory]
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want to analyze something, one useful approach is to try to start with
|
||||||
|
some smaller, minimal version of it, making observations about it, and then
|
||||||
|
generalizing those observations.
|
||||||
|
|
||||||
|
One particularly useful tool for analyzing and developing programming languages
|
||||||
|
is the **lambda calculus**, an amazingly simple abstract machine that can
|
||||||
|
represent all [Turing-complete] computation.
|
||||||
|
|
||||||
|
Due to its simplicity, there are numerous ways to extend it. In fact, the
|
||||||
|
standard way for researchers to introduce new language features is to extend the
|
||||||
|
lambda calculus with their feature, and prove properties about it.
|
||||||
|
|
||||||
|
Not only is it useful as a reasoning tool, some compilers may use a variant of
|
||||||
|
lambda calculus as an intermediate language for compilation. GHC famously uses a
|
||||||
|
variant of System F as its Core language.
|
||||||
|
|
||||||
|
[Turing-complete]: https://en.wikipedia.org/wiki/Turing_completeness
|
||||||
|
|
||||||
|
In this blog post series, I'll introduce the lambda calculus in its most basic
|
||||||
|
form, introduce some conventions when it comes to defining languages like this,
|
||||||
|
writing an interpreter for it, and then writing some extensions.
|
||||||
|
|
||||||
|
## Abstract machines
|
||||||
|
|
||||||
|
Firstly, what is an abstract machine? We think of real machines as mechanical
|
||||||
|
beings, that behave according to some rules. If I punch in 1 + 1 into my
|
||||||
|
calculator, and hit enter, I expect it to return 2.
|
||||||
|
|
||||||
|
In reality, there's all sorts of reasons that calculation could possibly fail,
|
||||||
|
battery too low, the chip is overheating, [cosmic rays], just to name a few.
|
||||||
|
From a mathematical point of view, these are all uninteresting failure cases we
|
||||||
|
would rather just ignore.
|
||||||
|
|
||||||
|
[cosmic rays]: https://en.wikipedia.org/wiki/Single-event_upset
|
||||||
|
|
||||||
|
What we could do is distill _just_ the rules into some form we can reason about.
|
||||||
|
We could just simulate its execution in our heads and confirm that the rules we
|
||||||
|
created are nice.
|
||||||
|
|
||||||
|
## The lambda calculus
|
||||||
|
|
||||||
|
The lambda calculus is essentially an abstract machine where the only operation
|
||||||
|
that's defined is function call. The only operations you need are:
|
||||||
|
|
||||||
|
- Make a function (which we call a "lambda", or $\lambda$)
|
||||||
|
- Call a function
|
||||||
|
|
||||||
|
That's it. That's all there is to it.
|
||||||
|
|
||||||
|
Here's the identity function:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\lambda x . x
|
||||||
|
$$
|
||||||
|
|
||||||
|
Here's a function that calls its first input on its second:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\lambda f . (\lambda x . f (x))
|
||||||
|
$$
|
||||||
|
|
||||||
|
Taking in multiple arguments can be represented by just nesting lambdas.
|
||||||
|
|
||||||
|
## Representing numbers
|
||||||
|
|
||||||
|
With a language this simple, how can you possibly do anything with it? Can you
|
||||||
|
even do arithmetic?
|
||||||
|
|
||||||
|
It turns out the answer is yes, as long as you _encode_ numbers in a particular
|
||||||
|
way. One clever way is by using [Church encoding]. Here are some of the first
|
||||||
|
few natural numbers written with Church encoding:
|
||||||
|
|
||||||
|
[Church encoding]: https://en.wikipedia.org/wiki/Church_encoding
|
||||||
|
|
||||||
|
- 0 is $\lambda s . \lambda z . z$
|
||||||
|
- 1 is $\lambda s . \lambda z . s(z)$
|
||||||
|
- 2 is $\lambda s . \lambda z . s(s(z))$
|
||||||
|
- 3 is $\lambda s . \lambda z . s(s(s(z)))$
|
||||||
|
|
||||||
|
See the pattern? $s$ represents a "successor" function, and $z$ represents zero.
|
||||||
|
Note that what we pass in for $s$ and $z$ don't matter, as long as $s$ is a
|
||||||
|
function.
|
||||||
|
|
||||||
|
With this, we can imagine a "plus" operator that simply takes 2 numbers $a$ and
|
||||||
|
$b$, then calls $a$ (since it's a function) using $b$ as the "zero" value. Here it is:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathsf{plus} :\equiv \lambda a . \lambda b . \lambda s . \lambda z . a(s)(b(s)(z))
|
||||||
|
$$
|
||||||
|
|
||||||
|
If this doesn't make sense, try plugging 2 and 3 from above and simplifying a
|
||||||
|
bit to convince yourself that this function works.
|
||||||
|
|
||||||
|
There's also ways to encode different things like rational numbers, booleans,
|
||||||
|
pairs, and lists. See the [Church encoding] Wikipedia page for more.
|
||||||
|
|
||||||
|
## Formal definition
|
||||||
|
|
||||||
|
Now that we have the intuition down, let's go on to a more formal definition.
|
||||||
|
|
||||||
|
The lambda calculus is a _language_. In this language we have terms that can get
|
||||||
|
evaluated. Terms are commonly described with a [grammar], which tell us how
|
||||||
|
terms in this language can be constructed. The grammar for the lambda calculus
|
||||||
|
is incredibly simple:
|
||||||
|
|
||||||
|
[grammar]: https://en.wikipedia.org/wiki/Formal_grammar
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathsf{Term} \; t ::= x \; | \; \lambda x . t \; | \; t(t)
|
||||||
|
$$
|
||||||
|
|
||||||
|
This says that there are only 3 possible terms:
|
||||||
|
|
||||||
|
1. $x$, which is our convention for some kind of variable or name
|
||||||
|
2. $\lambda x . t$, which creates a lambda function that takes an input called $x$ and returns another term $t$
|
||||||
|
3. $t(t)$ which is just function application, one term calling another
|
||||||
|
|
||||||
|
All of the terms above, including $\mathsf{plus}$, can be represented as a
|
||||||
|
combination of these constructors.
|
||||||
|
|
||||||
|
Great, but we can't really do anything if all we know is how to write down some
|
||||||
|
terms. We need some rules to tell us how to evaluate it. Something like this:
|
||||||
|
|
||||||
|
$$
|
||||||
|
(\lambda x . t_1) (t_2) \longmapsto \mathsf{subst}_{x \mapsto t_2} t_1
|
||||||
|
$$
|
||||||
|
|
||||||
|
Slightly inventing notation here, but essentially whenever we see a lambda being
|
||||||
|
applied to something, we should substitute the thing applied into the body of
|
||||||
|
the lambda everywhere we see that $x$. This isn't really a formal definition,
|
||||||
|
just kind of a vague descriptor. There's too many loose ends; what is
|
||||||
|
substitution? How do we keep track of what variables have which values? Where
|
||||||
|
does the result go? Let's go back and introduce a few more concepts to clean up
|
||||||
|
the formal definition.
|
||||||
|
|
||||||
|
### Contexts
|
||||||
|
|
||||||
|
### Substitution
|
||||||
|
|
||||||
|
### Values
|
||||||
|
|
||||||
|
### Judgments
|
||||||
|
|
||||||
|
### Rules
|
|
@ -225,7 +225,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
>p {
|
>p {
|
||||||
line-height: 1.25;
|
line-height: 1.5;
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
margin-top: 1.25rem;
|
margin-top: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue