lambda calculus
https://scienceblogs.com/taxonomy/term/27261/feed
enPrograms are Proofs: Models and Types in Lambda Calculus
https://scienceblogs.com/goodmath/2006/09/01/programs-are-proofs-models-and-1
<span>Programs are Proofs: Models and Types in Lambda Calculus</span>
<div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>Lambda calculus started off with the simple, untyped lambda calculus that we've been talking about so far. But one of the great open questions about lambda calculus was: was it sound? Did it have a valid model?</p>
<p>Church found that it was easy to produce some strange and non-sensical expressions using the simple lambda calculus. In order to try to work around those problems, and end up with a consistent system, Church introduced the concept of *types*, producing the *simply typed lambda calculus*. Once types hit the scene, things really went wild; the type systems for lambda calculi have never stopped developing: people are still finding new things to do by extending the LC type system today! Most lambda calculus based programming languages are based on the Hindley-Milner lambda calculus, which is a simplification of one of the standard sophisticated typed lambda calculi called *SystemF*. There's even a [Lambda Cube][cube], though it's not related to the Time Cube. Once people really started to understand types, they realized that the *untyped* lambda calculus was really just a pathologically simple instance of the simply typed lambda calculus: a typed LC with only one base type.</p>
<p>The semantics of lambda calculus are easiest to talk about in a typed version. For now, I'll talk about the simplest typed LC, known as the *simply typed lambda calculus*. One of the really amazing things about this, which I'll show, is that a simply typed lambda calculus is completely semantically equivalent to an intuitionistic propositional logic: each type in the program is a proposition in the logic; each β reduction corresponds to an inference step; and each complete function corresponds to a proof! Look below the fold for how.</p>
<!--more--><p>Types<br />
---------</p>
<p>The main thing that typed lambda calculus adds to the mix is a concept called *base types*. In a typed lambda calculus, you have some universe of atomic values which you can manipulate; those values are partitioned into the *base types*. Base types are usually named by single lower-case greek letters: So, for example, we could have a type "σ", which consists of the set of natural numbers; a type "τ" which corresponds to boolean true/false values; and a type "γ" which corresponds to strings.</p>
<p>Once we have basic types, then we can talk about the type of a function. A function maps from a value of one type (the type of parameter) to a value of a second type (the type of the return value). For a function that takes a parameter of type "γ", and returns a value of type "δ", we write its type as "γ → δ". "→" is called the _function type constructor_; it associates to the right, so "γ → δ → ε" parses as "γ → (δ → ε)"</p>
<p>To apply types to the lambda calculus, we do a couple of things. First, we need a syntax update so that we can include type information in lambda terms. And second, we need to add a set of rules to show what it means for a typed program to be valid.</p>
<p>The syntax part is easy. We add a ":" to the notation; the colon has an expression or variable binding on its left, and a type specification on its right. It asserts that whatever is on the left side of the colon has the type specified on the right side. A few examples:</p>
<p>1. "*λ x : ν . x + 3*". This asserts that the parameter, *x*, has type "ν", which we'll use as the type name for the natural numbers. (In case it's hard to tell, that's a greek letter "nu" for natural.) There is no assertion of the type of the result of the function; but since we know that "+" is a function with type "ν → ν", which can infer that the result type of this function will be "ν".<br />
2. "*(λ x . x + 3): ν → ν*". This is the same as the previous, but with the type declaration moved out, so that it asserts the type for the lambda expression as a whole. This time we can infer that "*x : ν*" because the function type is "ν → ν", which means that the function parameter has type "ν".<br />
3. "*λ x : ν, y : δ . if y then x \* x else x*". A two parameter function; the first parameter is type "ν", the second type "δ". We can infer the return type, which is "ν". So the type of the full function is "ν → δ → ν". This may seem surprising at first; but remember that lambda calculus really works in terms of single parameter functions; multi-parameter functions are a shorthand for currying. So really, this function is: "*λ x : ν . (λ y : δ . if y then x \* x else x)*"; the inner lambda is type "δ → ν"; the outer lambda is type "ν → (δ → ν)".</p>
<p>To talk about whether a program is *valid* with respect to types (aka *well-typed*), we need to introduce a set of rules for type inference. When the type of an expression is inferred using one of these rules, we call that a *type judgement*. Type inference and judgements allow us to reason about types in a lambda expression; and if any part of an expression winds up with an inconsistent type judgement, then the expression is invalid. (When Church started doing typed LC, one of the motivations was to distinguish between values representing "atoms", and values representing "predicates"; he was trying to avoid the Godel-esque paradoxes, by using types to ensure that predicates couldn't operate on predicates.)</p>
<p>Type judgements are usually written in a sequent-based notation, which looks like a fraction where the numerator consists of statements that we know to be true; and the denominator is what we can infer from the numerator. In the numerator, we normally have statements using a *context*, which is a set of type judgements that we already know; it's usually written as an uppercase greek letter. If a type context includes the judgement that "*x : γ*", I'll write that as "Γ :- x : γ". However, it's really difficult to write fraction-form sequence in HTML, so instead, I'll write in a textual form.</p>
<p>1. The type identity rule<br />**Given:** nothing,<br /> **Infer:** x:α :- x:α. <br />This is the simplest rule: if we have no other information except a declaration of the type of a variable, then we know that that variable has the type it was declared with.<br />
2. The type invariance rul:<br /> **Given:** Γ :- x:α, x != y,<br /> **Infer:** Γ + y:β :- x:α.<br /> This is a statement of non-interference. If we know that "x:α", then inferring a type judgement about any other term cannot change our type judgement for "x".<br />
3. The function type inference rule<br /> **Given:** Γ + x:α :- y:β,<br /> **Infer: Γ :- (λ x:α . y):α→β. <br /><br />
This statement allows us to infer function types given parameter types. Ff we know the type of the parameter to a function is "α"; and we know that the type of the term that makes up the body of the function is "β", then we know that the type of the function is "α→β".<br />
4. The function application inference rule<br /> **Given:** Γ :- x:α→β, Γ :- y:α, <br />**Infer:** Γ :- (x y):β.<br /><br />
If we know that a function has type "α→β", and we apply it to a value of type "α", the result is an term of type "β".</p>
<p>These four rules are it. If we can take a lambda expression, and come up with a consistent set of type judgements for every term in the expression, then the expression is well-typed. If not, then the expression is invalid.</p>
<p>Just for kicks, here's how we'd write types for the SKI combinators. These are incomplete types - I'm using type variables, rather than specific types. In a real program using the combinators, you could work out the necessary substitutions of concrete types for the type variables. Don't worry, I'll show you an example to clarify that.</p>
<p>* **I combinator**: (λ x . x):α→α<br />
* **K combinator**: (λ x:α.((λ y:β.x):β→&alpha)): α→β→α<br />
* **S combinator:** (λ x : α → β → γ.(λ y : α→β.(λ z:α.(xz:β→γ (yz:β)))):(α→β→γ)→(α→β)→γ</p>
<p>Now, let's look at a simple lambda calculus expression: "λ x y.y x". Without any type declarations or parameters, we don't know the exact type. But we do know that "x" has some type; we'll call that "α"; and we know that "y" is a function that will be applied with "x" as a parameter, so it's got parameter type α, but its result type is unknown. So using type variables, we can say "x:α,y:α→β". We can figure out what "α" and "β" are by looking at a complete expression. So, let's work out the typing of it with x="3", and y="λ a:ν.a\*a". We'll assume that our type context already includes "*" as a function of type "ν→ν→ν", where ν is the type of natural numbers.</p>
<p>* "(λ x y . y x) 3 (λ a:ν . a \* a)": Since 3 is a literal integer, we know its type: 3:ν.<br />
* By rule 4, we can infer that the type of the expression "a\*a" where "a:ν" is "ν", and \*:ν→ν→ν so therefore, by rule 3 the lambda expression has type "ν→ν". So with type labelling, our expression is now: "(λ x y . y x) (3:ν) (λ a:ν.(a\*a):ν) : ν→ν".<br />
* So - now, we know that the parameter "x" of the first lambda must be "ν"; and "y" must be "ν→ν"; so by rule 4, we know that the type of the application expression "y x" must be "ν"; and then by rule 3, the lambda has type: "ν→(ν→ν)→ν.<br />
* So, for this one, both α and β end up being "ν", the type of natural numbers.</p>
<p>So, now we have a simply typed lambda calculus. The reason that it's simply typed is because the type treatment here is minimal: the only way of building new types is through the unavoidable "->" constructor. Other typed lambda calculi include the ability to define *parametric types*, which are types expressed as functions ranging over types.</p>
<p>Programs are Proofs!<br />
------------------------<br />
Think about the types in the simple typed language calculus. Anything which can be formed from the following grammar is a lambda calculus type:</p>
<p>type ::= primitive | function | ( type )<br /><br />
primitive ::= α | β | δ | ...<br /><br />
function ::= type→type</p>
<p>The catch with that grammar is that you can create type expressions which, while they are valid type definitions, you can't write a single, complete, closed expression which will actually have that type. (A closed expression is one with no free variables.) When there is an expression that has a type, we say that the expression *inhabits* the type; and that the type is an inhabited type. If there is no expression that can inhabit a type, we say it's *uninhabitable*.</p>
<p> So what's the difference between inhabitable type, and an uninhabitable type?</p>
<p>The answer comes from something called the **Curry-Howard isomorphism**. For a typed lambda calculus, there is a corresponding [intuitionistic logic][intuitionistic]; a type expression is inhabitable if and only if the type is a *provable theorem* in the corresponding logic.</p>
<p>Look at the type "α→α". Now, instead of seeing "→" as the function type constructor, try looking at it as logical implication. "α implies α" is clearly a theorem of intuitionistic logic. So the type "α→α" is inhabitable. </p>
<p>Now, look at "α→β". That's *not* a theorem, unless there's some other context that proves it. As a function type, that's the type of a function which, without including any context of any kind, can take a parameter of type α, and return a value of a *different* type β. You can't do that - there's got to be some context which provides a value of type β - and to access the context, there's got to be something to allow the function to access its context: a free variable. Same thing in the logic and the lambda calculus: you need some kind of context to establish "α→β" as a theorem (in the logic) or as an inhabitable type (in the lambda calculus).</p>
<p>It gets better. If there is a function whose type is a theorem in the corresponding intuitionistic logic, then the program that has that type *is a proof* of the theorem. Each beta reduction is equivalent to an inference step in the logic.</p>
<p>The logic corresponding to the lambda calculus *is* it's model. In some sense, the lambda calculus and intuitionistic logic are just different reflections of the same thing.</p>
<p>[intuitionistic]: <a href="http://goodmath.blogspot.com/2006/05/logic-fun-intuitionistic-logic.html">http://goodmath.blogspot.com/2006/05/logic-fun-intuitionistic-logic.html</a></p>
</div>
<span><a title="View user profile." href="https://scienceblogs.com/goodmath" lang="" about="https://scienceblogs.com/goodmath" typeof="schema:Person" property="schema:name" datatype="" xml:lang="">goodmath</a></span>
<span>Fri, 09/01/2006 - 02:30</span>
Fri, 01 Sep 2006 06:30:00 +0000goodmath92093 at https://scienceblogs.comWhy oh why Y?
https://scienceblogs.com/goodmath/2006/08/31/why-oh-why-y
<span>Why oh why Y?</span>
<div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>So in the last few posts, I've been building up the bits and pieces that turn lambda calculus into a useful system. We've got numbers, booleans, and choice operators. The only thing we're lacking is some kind of repetition or iteration.</p>
<p>In lambda calculus, all iteration is done by recursion. In fact, recursion is a pretty natural way of expressing iteration. It takes a bit of getting used to, but anyone who's programmed in a language like Scheme, ML, or Haskell for a while gets very used to idea, and feels frustrated coming back to a language like Java, where you need to write loops. </p>
<p>It can be a bit difficult if you're not used to thinking recursively. I wrote [an explanation of recursion][recursion] which you can go read if you're not used to what recursion is or how it works. </p>
<p>be found <a href="">here</a>). But since functions in lambda calculus don't have names, that means that we resort to something tricky. It's called the Y combinator, aka the lambda fixed point operator.</p>
<p>Let's start by looking at a simple recursive function outside of the lambda calculus. The factorial function, n!, is the standard example:</p>
<p><code><br />
factorial(n) = 1 if n = 0,<br />
or factorial(n) = n*factorial(n-1) if n > 0<br /></code></p>
<p>If we want to start trying to write that in lambda calculus, we'd need a couple of tools... We need a test for equality to zero, and we need a way of multiplying numbers; and we need a way of subtracting one.</p>
<p>For testing equality to zero, we'll use a function named *IsZero*, which takes three parameters: a number, and two values. If the number is 0, it returns the first value; if it's not 0, then it returns the second value.</p>
<p>For multiplication - multiplication is an iterative algorithm, so we can't write multiplication until we work out recursion. But we'll just handwave that for now, and have a function *Mult x y*.</p>
<p>And finally, for subtracting one, we'll use *Pred x* for the predecessor of x - that is, x - 1.</p>
<p>So - a first stab at factorial, written with the recursive call left with a blank in it, would be:</p>
<p></p><center><br />
*λ n . IsZero n 1 (Mult n (**something** (Pred n)))*<br /></center>
<p>Now, the question is, what kind of "something" can we plug in to there? What we're really like to do is plug in a copy of the function itself:</p>
<p></p><center><br />
*Fact ≡ λ n . IsZero n 1 (Mult n (Fact (Pred n)))*<br /></center>
<p>How can we do that? Well, the usual way of plugging something in to a lambda calculus function is through a parameter:</p>
<p></p><center><br />
*Fact ≡ (λ f n . IsZero n 1 (Mult n (f (Pred n)))) Fact*<br /></center>
<p>Of course, we can't plug in a copy of the function as its own parameter that way: the name *Fact* doesn't exist in the expression in which we're trying to use it. You can't use an undefined name - and in lambda calculus, the *only* way to bind a name is by passing it as a parameter to a λ expression. So what can we do?</p>
<p>The answer is to use something called a *combinator*. A combinator is a special kind of *higher order function* which can be defined without reference to anything but function applications. (A higher order function is a function which takes functions as parameters and returns functions as results). The Y combinator is the special, almost magical function that makes recursion possible. Here's what it looks like:</p>
<p></p><center><br />
Y ≡ λ y . (λ x . y (x x)) (λ x . y (x x))<br /></center>
<p>If you look at it, the reason for calling it Y is because it is *shaped* like a Y. To show you that more clearly, sometimes we write lambda calculus using trees. Here's the tree for the Y combinator:</p>
<p><img src="http://homepage.mac.com/markcc/lambda/Y.jpg" /></p>
<p>Why is the Y combinator an answer to our problem in defining the factorial function? The Y combinator is something called a *fixed point* combinator. What makes it special is the fact that for any function *f*, *Y f* evaluates to *f Y f*; which evaluates to *f (f Y f)*; which evaluates to *f (f (f Y f))*. See why it's called Y?</p>
<p>Let's try walking through "*Y f*":</p>
<p>1. Expand Y: "*(λ y . (λ x . y (x x)) (λ x . y (x x))) f*"<br />
2. β: "*(λ x . f (x x)) (λ x . f (x x))*<br />
3. β again: "*f (λ x . f (x x)) (λ x . f (x x))*"<br />
4. Since "*Y f = (λ x . f (x x)) (λ x . f (x x))*", what we just got in step three is "f Y f".</p>
<p>See, there's the magic of "Y". No matter what you do, you can't make it consume itself. Evaluating "*Y f*" will produce another copy of *f*, and leave the "Y f" part untouched.</p>
<p>So how do we use this crazy thing?</p>
<p>Remember our last attempt at defining the factorial function? Let's look at it again:</p>
<p></p><center><br />
*Fact ≡ (λ f n . IsZero n 1 (Mult n (f (Pred n)))) Fact*<br /></center>
<p>Let's rewrite it just a tiny bit to make it easier to talk about:</p>
<p></p><center><br />
*Metafact ≡ (λ f n . IsZero n 1 (Mult n (f (Pred n))))*<br /><br />
*Fact ≡ Metafact Fact*<br /></center>
<p>Now - the trick is, "Fact" is not an identifier defined inside of "Fact". How do we let "Fact" reference "Fact"? Well, we did a lambda abstraction to let us pass the "Fact" function as a parameter; so what we needed to do is to find a way to write "Fact" that lets us pass it to itself as a parameter.</p>
<p>What does "Y f" do? It expands into a call to "f" with "Y f" as its first parameter. And "Y f" will expand into "f Y f" - that is, a call to "f" with "Y f" as its parameter. In other words, Y f turns "f" into a recursive function with *itself* as its first parameter.</p>
<p>So the factorial function is:</p>
<p></p><center><br />
*Fact ≡ Y Metafact*<br /></center>
<p>*(Y metafact)* is the parameter value of "f" in the metafact lambda; when we do β on the function, if n is zero, then it just returns 1. If it's not zero, then we get the call to *f (Pred n)*. *f* betas to *Y metafact*. Which does that funky magic copying thing, giving us *metafact (Y metafact) (Pred n)*.</p>
<p>Voila, recursion. s</p>
<p>I learned about the Y combinator back in my undergrad days, which would place it around 1989 - and I still find it rather mystifying. I do understand it now, but I can't imagine how on earth anyone ever figured it out!</p>
<p>If you're interested in this, then I <em>highly</em> recommend getting a copy of the book [The Little Schemer][schemer]. It's a wonderful little book - set up like a childrens' book, where the front of each page is a question; and the back of each page is the answer. It's written in a delightfully playful style, it's very fun and engaging, and it will not only teach you to program in Scheme.</p>
<p>As an important side-note there are actually a couple of different versions of the Y combinator. There are different ways of evaluating lambda calculus: given an expression like *(λ x y . x \* y) 3 ((λ z. z \* z) 4)*"<br />
we can do it in two different orders: we can first do the beta on "*(λ x y . x \* y)*",which would give us: "*3 \* ((λ z . z \* z) 4)*".</p>
<p>Or, we could beta "*((λ z . z \* z) 4)*" first: "*(λ x y . x \* y) 3 (4 \* 4)*". Nn this case, the two orders end up with the same result; but that's not always the case. Sometimes the order of evaluation matters - and the way that the Y combinator works is one of those times. One order will result in expanding the Y infinitely; the other will result in a clean recursive function.</p>
<p>The first order is what we call *lazy evaluation*: don't evaluate the parameters to a function until they're needed. (This is also pretty much the same thing as what we sometime call *by name* parameter passing.) The second is called *eager evaluation* : always evaluate parameters *before* the functions that they're passed to. (In real programming languages, Lisp, Scheme, and ML are lambda-calculus based languages that use eager evaluation; Haskell and Miranda are lambda calculus based languages that use lazy evaluation.) The Y combinator I described above is the Y for *lazy* evaluation. If we used eager evaluation, then Y combinator above wouldn't work - in fact, it would copy Ys forever. There is another version of Y which works for eager evaluation - in fact, it's the one described in "The Little Schemer", and they explain it so much better than I do that I'll just recommend again that you head for whatever bookstore you prefer, and buy yourself a copy.</p>
<p>[recursion]: <a href="http://goodmath.blogspot.com/2006/03/clarifying-recursion.html">http://goodmath.blogspot.com/2006/03/clarifying-recursion.html</a><br />
[schemer]: <a href="http://www.amazon.com/gp/redirect.html?link_code=ur2&tag=goodmathbadma-20&camp=1789&creative=9325&location=/gp/search%3F%26index=books%26keywords=little%20schemer%26_encoding=UTF8">http://www.amazon.com/gp/redirect.html?link_code=ur2&tag=goodmathbadma-…</a></p>
</div>
<span><a title="View user profile." href="https://scienceblogs.com/goodmath" lang="" about="https://scienceblogs.com/goodmath" typeof="schema:Person" property="schema:name" datatype="" xml:lang="">goodmath</a></span>
<span>Thu, 08/31/2006 - 03:12</span>
Thu, 31 Aug 2006 07:12:17 +0000goodmath92092 at https://scienceblogs.comThe Genius of Alonzo Church (rerun)
https://scienceblogs.com/goodmath/2006/08/30/church-numerals-rerun
<span>The Genius of Alonzo Church (rerun)</span>
<div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>I'm on vacation this week, so I'm posting reruns of some of the better articles from when Goodmath/Badmath was on Blogger. Todays is a combination of two short posts on numbers and control booleans in λ calculus.</p>
<p>So, now, time to move on to doing interesting stuff with lambda calculus. To<br />
start with, for convenience, I'll introduce a bit of syntactic sugar to let us<br />
name functions. This will make things easier to read as we get to complicated<br />
stuff.</p>
<p>To introduce a *global* function (that is a function that we'll use throughout our lambda calculus introduction without including its declaration in every expression), we'll use a definition like the following:</p>
<p></p><center>*square ≡ λ x . x × x*</center>
<p>This declares a function named "square", whose definition is "*λ x . x×x*". If we had an expression "square 4", the definition above means that it would effectively be treated as if the expression were: "*(λ square . square 4)(λ x . x×x)*".</p>
<p>Numbers in Lambda Calculus<br />
------------------------------</p>
<p>In some of the examples, I used numbers and arithmetic operations. But numbers don't really exist in lambda calculus; all we really have are functions! So we need to invent some way of <em>creating</em> numbers using functions. Fortunately, Alonzo Church, the genius who invented the lambda calculus worked out how to do that. His version of numbers-as-functions are called <em>Church Numerals</em>.</p>
<p>In Church numerals, all numbers are functions with two parameters:</p>
<p>* Zero ≡ *λ s z . z*<br />
* One ≡ *λ s z . s z*<br />
* Two ≡ *λ s z . s (s z)*<br />
* Three ≡ *λ s z . s (s (s z))*<br />
* Any natural number "n", is represented by a Church numeral which is a function which applies its first parameter to its second parameter "n" times.</p>
<p>A good way of understanding this is to think of "z" as being a a name for a zero-value, and "s" as a name for a successor function. So zero is a function which just returns the "0" value; one is a function which applies the successor function once to zero; two is a function which applies successor to the successor of zero, etc. It's just the Peano arithmetic definition of numbers transformed into lambda calculus. </p>
<p>But the really cool thing is what comes next. If we want to do addition, x + y, we need to write a function with four parameters; the two numbers to add; and the "s" and "z" values we want in the resulting number:</p>
<p></p><center>add ≡ *λ s z x y . x s (y s z)*</center>
<p>Let's curry that, to separate the two things that are going on. First, it's taking two parameters which are the two values we need to add; second, it needs to normalize things so that the two values being added end up sharing the same binding of the zero and successor values.</p>
<p></p><center>add_curry ≡ λ x y. (λ s z . (x s (y s z)))</center>
<p>Look at that for a moment; what that says is, to add x and y: create the church numeral "y" using the parameters "s" and "z". Then **apply x** to that new church numeral y. That is: a number is a function <em>which adds itself to another number</em>.</p>
<p>Let's look a tad closer, and run through the evaluation of 2 + 3:</p>
<p></p><center>add_curry (λ s z . s (s z)) (λ s z . s (s (s z)))</center>
<p>To make things easier, let's alpha 2 and 3, so that "2" uses "s2" and "z2", and 3 uses "s3" and "z3";</p>
<p></p><center>add_curry (λ s2 z2 . s2 (s2 z2)) (λ s3 z3 . s3 (s3 (s3 z3)))</center>
<p>Now, let's do replace "add_curry" with its definition:</p>
<p></p><center><br />
(λ x y .(λ s z. (x s y s z))) (λ s2 z2 . s2 (s2 z2)) (λ s3 z3 . s3 (s3 (s3 z3))) </center>
<p>Now, let's do a beta on add:</p>
<p></p><center><br />
λ s z . (λ s2 z2 . s2 (s2 z2)) s (λ s3 z3 . s3 (s3 (s3 z3)) s z)</center>
<p>And now let's beta the church numeral for three. This basically just "normalizes" three: it replaces the successor and zero function in the definition of three with the successor and zero functions from the parameters to add.</p>
<p></p><center>λ s z . (λ s2 z2 . s2 (s2 z2)) s (s (s (s z)))</center>
<p>Now.. Here comes the really neat part. Beta again, this time on the lambda for two. Look at what we're going to be doing here: two is a function which takes two parameters: a successor function, and zero function. To add two and three, we're using the successor function from add function; and we're using the result of evaluating three *as the value of the zero!* for two:</p>
<p></p><center>λ s z . s (s (s (s (s z))))</center>
<p>And we have our result: the church numeral for five!</p>
<p>Choice in Lambda Calculus<br />
---------------------------</p>
<p>Now that we have numbers in our Lambda calculus, there are only two things missing before we can express arbitrary computations: a way of expressing choice, and a way of expressing repetition. So now I'll talk about booleans and choice; and then next post I'll explain repetition and recursion.</p>
<p>We'd like to be able to write choices as if/then/else expressions, like we have in most programming languages. Following the basic pattern of the church numerals, where a number is expressed as a function that adds itself to another number, we'll express true and false values as functions that perform an if-then-else operation on their parameters. These are sometimes called *Church booleans*. (Of course, also invented by Alonzo Church.)</p>
<p>* TRUE ≡ λ t f . t<br />
* FALSE ≡ λ t f . f</p>
<p>So, now we can write an "if" function, whose first parameter is a condition expression, second parameter is the expression to evaluate if the condition was true, and third parameter is the expression to evaluate if the condition is false.</p>
<p>* IfThenElse ≡ *λ cond t f . cond t f*</p>
<p>For the boolean values to be useful, we also need to be able to do the usual logical operations:</p>
<p>* BoolAnd ≡ *λ x y .x y FALSE*<br />
* BoolOr ≡ *λ x y. x TRUE y*<br />
* BoolNot ≡ *λ x . x FALSE TRUE*<br /></p>
<p>Now, let's just walk through those a bit. Let's first take a look at BoolAnd. We'll try evaluating "*BoolAnd TRUE FALSE*":</p>
<p>* Expand the TRUE and FALSE definitions: "*BoolAnd (λ t f . t) (λ t f . f)*"<br />
* Alpha the true and false: "*BoolAnd (λ tt tf . tt) (λ ft ff . ff)*"<br />
* Now, expand BoolAnd: *(λ t f. t f FALSE) (λ tt tf . tt) (λ ft ff . ff)*<br />
* And beta: *(λ tt tf.tt) (λ ft ff. ff) FALSE*<br />
* Beta again: *(λ xf yf . yf)*</p>
<p>And we have the result: *BoolAnd TRUE FALSE = FALSE*. Now let's look at "*BoolAnd FALSE TRUE*":</p>
<p>* *BoolAnd (λ t f . f) (λ t f .t)*<br />
* Alpha: *BoolAnd (λ ft ff . ff) (λ tt tf . tt)*<br />
* Expand BoolAnd: *(λ x y .x y FALSE) (lambda ft ff . ff) (lambda tt tf . tt)*<br />
* Beta: *(λ ft ff . ff) (lambda tt tf . tt) FALSE<br />
* Beta again: FALSE</p>
<p>So *BoolAnd FALSE TRUE = FALSE*. Finally, *BoolAnd TRUE TRUE*:</p>
<p>* Expand the two trues: *BoolAnd (λ t f . t) (λ t f . t)*<br />
* Alpha: *BoolAnd (λ xt xf . xt) (λ yt yf . yt)*<br />
* Expand BoolAnd: *(λ x y . x y FALSE) (λ xt xf . xt) (λ yt yf . yt)*<br />
* Beta: *(λ xt xf . xt) (λ yt yf . yt) FALSE*<br />
* Beta: (λ yt yf . yt)</p>
<p>So *BoolAnd TRUE TRUE = TRUE*.</p>
<p>The other booleans work in much the same way.</p>
<p>So by the genius of Alonzo church, we have *almost* everything we need in order to write programs in λ calculus.</p>
</div>
<span><a title="View user profile." href="https://scienceblogs.com/goodmath" lang="" about="https://scienceblogs.com/goodmath" typeof="schema:Person" property="schema:name" datatype="" xml:lang="">goodmath</a></span>
<span>Wed, 08/30/2006 - 02:33</span>
Wed, 30 Aug 2006 06:33:54 +0000goodmath92091 at https://scienceblogs.comA Lambda Calculus rerun
https://scienceblogs.com/goodmath/2006/08/29/a-lambda-calculus-rerun-1
<span>A Lambda Calculus rerun</span>
<div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><p>I'm on vacation this week, so I'm recycling some posts that I thought were particularly interesting to give you something to read.</p>
<p>------------</p>
<p>In computer science, especially in the field of programming languages, we tend to use one particular calculus a lot: the Lambda calculus. Lambda calculus is also extensively used by logicians studying the nature of computation and the structure of discrete mathematics. Lambda calculus is great for a lot of reasons, among them:</p>
<p>1. It's very simple.<br />
2. It's Turing complete.<br />
3. It's easy to read and write.<br />
4. It's semantics are strong enough that we can do reasoning from it.<br />
5. It's got a good solid model.<br />
6. It's easy to create variants to explore the properties of various alternative ways of structuring computations or semantics.</p>
<p>The ease of reading and writing lambda calculus is a big deal. It's led to the development of a lot of extremely good programming languages based, to one degree or another, on the lambda calculus: Lisp, ML, and Haskell are very strongly lambda calculus based.</p>
<p>The lambda calculus is based on the concept of *functions*>. In the pure lambda calculus, *everything* is a function; there are no values *at all* except for functions. But we can build up anything we need using functions. Remember back in the early days of this blog, I talked a bit about how to build mathematics? We can build the entire structure of mathematics from nothing but lambda calculus.</p>
<p>So, enough lead-in. Let's dive in a look at LC. Remember that for a calculus, you need to define two things: the syntax, which describes how valid expressions can be written in the calculus; and a set of rules that allow you to symbolically manipulate the expressions.</p>
<p>Lambda Calculus Syntax<br />
-------------------------</p>
<p>The lambda calculus has exactly three kinds of expressions:</p>
<p>1. Function definition: a function in lambda calculus is an expression, written: "*λ param . body*" which defines a function with one parameter.<br />
2. Identifier reference: an identifier reference is a name which matches the name of a parameter defined in a function expression enclosing the reference.<br />
3. Function application: applying a function is written by putting the function value in front of its parameter, as in "*x y*" to apply the function "*x*" to the value "*y*".</p>
<p>### Currying</p>
<p>There's a trick that we play in lambda calculus: if you look at the definition above, you'll notice that a function (lambda expression) only takes one parameter. That seems like a very big constraint - how can you even implement addition with only one parameter?</p>
<p>It turns out to be no problem, because of the fact that *functions are values*. So instead of writing a two parameter function, you can write a one parameter function that returns a one parameter function - in the end, it's effectively the same thing. It's called currying, after the great logician Haskell Curry.</p>
<p>For example, suppose we wanted to write a function to add x and y. We'd like to write something like: "*λ x y . x + y*". The way we do that with one-parameter functions is: we first write a function with one parameter, which returns *another* function with one parameter. </p>
<p>Adding x plus y becomes writing a one-parameter function with parameter x, which returns another one parameter function which adds x to its parameter: </p>
<p></p><center>*λ x . (λ y . x + y)*</center>
<p>Now that we know that adding multiple parameter functions doesn't *really* add anything but a bit of simplified syntax, we'll go ahead and use them when it's convenient.</p>
<p>### Free vs Bound Identifiers</p>
<p>One important syntactic issue that I haven't mentioned yet is *closure* or *complete binding*. For a lambda calculus expression to be evaluated, it cannot reference any identifiers that are not *bound*. An identifier is bound if it a parameter in an enclosing lambda expression; if an identifier is *not* bound in any enclosing context, then it is called a *free* variable.</p>
<p>1. *λ x . plus x y*: in this expression, "y" and "plus" are free, because they're not the parameter of any enclosing lambda expression; x is bound because it's a parameter of the function definition enclosing the expression "plus x y" where it's referenced.<br />
2. *λ x y.y x*: in this expression both x and y are bound, because they are parameters of the function definition.<br />
3. *λ y . (λ x . plus x y*: In the inner lambda, "*λ x . plus x y*", y and plus are free and x is bound. In the full expression, both x and y are bound: x is bound by the inner lambda, and y is bound by the other lambda. "plus" is still free.</p>
<p>We'll often use "free(x)" to mean the set of identifiers that are free in the expression "x".</p>
<p>A lambda calculus expression is completely valid only when all of its variables are bound. But when we look at smaller subexpressions of a complex expression, taken out of context, they can have free variables - and making sure that the variables that are free in subexpressions are treated right is very important.</p>
<p>## Lambda Calculus Evaluation Rules</p>
<p>There are only two real rules for evaluating expressions in lambda calculus; they're called α and beta. α is also called "conversion", and beta is also called "reduction".</p>
<p>### α Conversion</p>
<p>α is a renaming operation; basically it says that the names of variables are unimportant: given any expression in lambda calculus, we can change the name of the parameter to a function as long as we change all free references to it inside the body.</p>
<p>So - for instance, if we had an expression like:<br /></p><center> *λ x . if (= x 0) then 1 else x^2*</center>
<p>We can do α to replace X with Y (written "α[x/y]" and get):</p>
<p></p><center>*λ y . if (= y 0) then 1 else y^2*</center>
<p>Doing α does *not* change the meaning of the expression in any way. But as we'll see later, it's important because it gives us a way of doing things like recursion.</p>
<p>### β Reduction</p>
<p>β reduction is where things get interesting: this single rule is all that's needed to make the lambda calculus capable of performing *any* computation that can be done by a machine.</p>
<p>Beta basically says that if you have a function application, you can replace it with a copy of the body of the function with references to the parameter identifiers replaced by references to the parameter value in the application. That sounds confusing, but it's actually pretty easy when you see it in action.</p>
<p>Suppose we have the application expression: "*(λ x . x + 1) 3*". What beta says is that we can replace the application by taking the body of the function (which is "*x + 1*"); and replacing references to the parameter "*x*" by the value "*3*"; so the result of the beta reduction is "*3 + 1*".</p>
<p>A slightly more complicated example is the expression:</p>
<p></p><center>*λ y . (λ x . x + y)) q*</center>
<p>It's an interesting expression, because it's a lambda expression that when applied, results in another lambda expression: that is, it's a function that creates functions. When we do beta reduction in this, we're replacing all references to the parameter "y" with the identifier "q"; so, the result is "*λ x. x+q*".</p>
<p>One more example, just for the sake of being annoying:<br />
"*(λ x y. x y) (λ z . z × z) 3*". That's a function that takes two parameters, and applies the first one to the second one. When we evaluate that, we replace the parameter "*x*" in the body of the first function with "*λ z . z × z*"; and we replace the parameter "*y*" with "3", getting: "*(λ z . z × z) 3*". And we can perform beta on that, getting "*3 × 3*".</p>
<p>Written formally, beta says:</p>
<p></p><center><br />
λ x . B e = B[x := e] if free(e) ⊂ free(B[x := e]<br /></center>
<p>That condition on the end, "if free(e) subset free(B[x := e]" is why we need α: we can only do beta reduction *if* doing it doesn't create any collisions between bound identifiers and free identifiers: if the identifier "z" is free in "e", then we need to be sure that the beta-reduction doesn't make "z" become bound. If there is a name collision between a variable that is bound in "B" and a variable that is free in "e", then we need to use α to change the identifier names so that they're different.</p>
<p>As usual, an example will make that clearer: Suppose we have a expression defining a function, "*λ z . (λ x . x+z)*". Now, suppose we want to apply it:</p>
<p></p><center>*(λ z . (λ x . x + z)) (x + 2)*</center>
<p>In the parameter "*(x + 2)*", x is free. Now, suppose we break the rule and go ahead and do beta. We'd get "*λ x . x + x + 2*".</p>
<p>The variable that was *free* in "x + 2" is now bound. Now suppose we apply that function: "*(λ x . x + x + 2) 3*". By beta, we'd get "3 + 3 + 2", or 8.</p>
<p>What if we did α the way we were supposed to?</p>
<p>By α[x/y], we would get "*λ z . (λ y . y + z) (x+2)*".<br />
by α[x/y]: <code>lambda z . (lambda y . y+z)) (x + 2)</code>. Then by β, we'd get "*λ y . y + x + 2*". If we apply this function the way we did above, then by β, we'd get "*3+x+2*".</p>
<p>"*3+x+2*" and "*3+3+2*" are very different results!</p>
<p>And that's pretty much it. There's another *optional* rule you can add called η-conversion. η is a rule that adds *extensionality*, which provides a way of expressing equality between functions.</p>
<p>η says that in any lambda expression, I can replace the value "f" with the value "g" if/f for all possible parameter values x, *f x = g x*.</p>
<p>What I've described here is Turing complete - a full effective computation system. To make it useful, and see how this can be used to do real stuff, we need to define a bunch of basic functions that allow us to do math, condition tests, recursion, etc. I'll talk about those in my next post.</p>
<p>We also haven't defined a model for lambda-calculus yet. (I discussed models <a href="http://goodmath.blogspot.com/2006/04/more-logic-models-and-why-they-matter.html">here</a> and <a href="http://goodmath.blogspot.com/2006/04/correcting-my-models-post-or-why.html">here</a>.) That's actually quite an important thing! LC was played with by logicians for several years before they were able to come up with a complete model for it, and it was a matter of great concern that although LC looked correct, the early attempts to define a model for it were failures. After all, remember that if there isn't a valid model, that means that the results of the system are meaningless!</p>
</div>
<span><a title="View user profile." href="https://scienceblogs.com/goodmath" lang="" about="https://scienceblogs.com/goodmath" typeof="schema:Person" property="schema:name" datatype="" xml:lang="">goodmath</a></span>
<span>Tue, 08/29/2006 - 02:30</span>
Tue, 29 Aug 2006 06:30:00 +0000goodmath92090 at https://scienceblogs.com