[Logic and Computational Theory] Lambda calculus-opening

The original text comes from the series of Good Math/Bad Math . The full text is divided into 7 chapters. This is the first chapter . The Chinese blog Negative Xuan Xiaohua translated the first 6 chapters of this series. OCD said that "there is no more below", so I made a complete set by myself. Only the original text is translated here, and the version of "Negative Xuan Xiaohua" adds a lot of stories to make it more interesting to read.

Good Math, Bad Math Lambda calculus series 7

(In the original version of this post, I tried to use a JavaScript tool to generate MathML. But it didn’t go well: a few browsers couldn’t render correctly, and it didn’t display well in the RSS feed. So I had to start from scratch. , Rewrite it in a simple text format.)

 

My favorite Lambda calculus-opening

Computer science, especially programming languages, often tends to use a specific calculus: Lambda Calculus (Lambda Calculus). This kind of calculus is also widely used by logicians to study the nature of the structure of calculations and discrete mathematics. There are many reasons why Lambda calculus is great, including:

  • very simple.
  • Turing is complete.
  • Easy to read and write.
  • The semantics are strong enough to make (arbitrary) inferences from it.
  • It has a good physical model.
  • It is easy to create variants so that we can explore various properties of constructing computational or semantic ways.

Lambda calculus is easy to read and write, which is very important. It has led people to develop many excellent programming languages, which are based on Lambda calculus to varying degrees: LISP, ML and Haskell languages ​​are extremely dependent on Lambda calculus.

Lambda calculus is based on the concept of functions. In pure Lambda calculus, everything is a function, and there is no concept of value. However, we can use functions to build anything we need. Remember in the early days of this blog, I talked about some methods on how to build mathematics? We can use Lambda calculus to build the entire structure of mathematics from scratch.

Without further ado, let's take a closer look at LC (Lambda Calculus). For a calculus, two things need to be defined: grammar, which describes how to write legal expressions in calculus; and a set of rules that allow you to manipulate expressions symbolically.

Grammar of Lambda Calculus

Lambda calculus has only three types of expressions:

  1. Function definition: The function in Lambda calculus is an expression, written as:, lambda x . bodywhich means "a xfunction whose parameter parameter is , and its return value is bodythe calculation result." At this time, we say: Lambda expression binds parameters x.
  2. Identifier reference: An identifier reference is a name that is used to match a parameter name in a function expression.
  3. Function application: Function application is written in the form of putting the function value in front of its parameters, such as (lambda x . plus x x) y.

Currying

There is a trick in Lambda calculus: if you look at the above definition, you will find that a function (Lambda expression) only accepts one parameter. This seems to be a big limitation-how can you achieve addition with only one parameter?

There is no problem at all, because functions are values. You can write a function with only one parameter, and this function returns a function with one parameter, so that you can write a function with two parameters—essentially the two are the same. This is called Currying, named after the great logician Haskell Curry.

For example, we want to write a function to achieve x + y. We are more accustomed to write lambda x y . plus x ysomething like: something like this. The way to write a function with a single parameter is: we write a function with only one parameter and let it return another function with only one parameter. So x + yit becomes a one-parameter xfunction, which returns another function, which will be xadded to its own parameters:

lambda x. ( lambda y. plus x y )

Now we know that the function that adds multiple parameters does not really add anything, but simplifies the syntax, so when I continue to introduce it, I will use the multi-parameter function when it is convenient.

Free Identifier vs. Binding Identifier

There is an important grammatical issue that I haven't mentioned: closure or complete binding. When evaluating a Lambda calculus expression, you cannot quote any unbound identifiers. If an identifier is a parameter of a closed Lambda expression, we say that the identifier is (by) bound; if an identifier is not bound in any closed context, then it is called a free variable.

  • lambda x . plus x y: In this expression, yand plusare free because they are not any arguments Lambda expressions are closed; and xis binding, because it is a closed-form expressions defined function plus x yparameters.
  • lambda x y . y x : In this expression xand yare bound, because they are all parameters in the function definition.
  • lambda y . (lambda x . plus x y): In the inner calculus lambda x . plus x yin yand plusis free, xit is binding. In the complete expression, the xsum yis bound: xbound by the inner layer, and bound yby the rest of the calculus. plusStill free.

We will often use it free(x)to represent xfree identifiers in expressions .

A Lambda calculus expression is completely valid only when all its variables are bound. However, when we leave the context and focus on the sub-expression of a complex expression, free variables are allowed-at this time it is very important to know which variables in the sub-expression are free.

Lambda calculus algorithm

Lambda calculus has only two real laws: called Alpha and Beta. Alpha is also called "conversion", and Beta is also called "protocol".

Alpha conversion

Alpha is a renaming operation; basically, the name of the variable is not important: given any expression in the Lambda calculus, we can modify the name of the function parameter, as long as we modify all the free references to it in the function body at the same time .

So - for example, if there is an expression like this:

lambda x . if (= x 0) then 1 else x ^ 2 

We can use Alpha conversion to xbecome y(writing alpha[x / y]), so we have:

lambda y . if (= y 0) then 1 else y ^ 2 

This will not change the meaning of the expression at all. However, as we will see later, this is important because it allows us to implement things like recursion.

Beta Protocol

The Beta Statute is wonderful: this rule enables Lambda calculus to perform any calculation that can be done by a machine.

Beta basically means that if you have a function application, you can replace the part of the function body that is related to the corresponding function identifier. The replacement method is to replace the identifier with the parameter value. This sounds confusing, but it is very easy to use.

Suppose we have a function application expression: "  (lambda x . x + 1) 3 ". The so-called Beta protocol is that we can x + 1implement function application by replacing the function body (ie " "), and 3replace the referenced parameter" x" with the value " ". So the result of the Beta Statute is " 3 + 1".

A slightly more complex example: (lambda y . (lambda x . x + y)) q. This is an interesting expression, because the result of applying this Lambda expression is another Lambda expression: that is, it is a function that creates a function. In the Beta protocol at this time, it is necessary to qreplace all reference parameters " y" with the identifier " ". So, the result is "  lambda x . x + q ".

Give another example that makes you even more uncomfortable: "  (lambda x y. x y) (lambda z . z * z) 3 ". This is a function with two parameters, and it (its function is) applies the first parameter to the second parameter. When we calculate, we replace the parameter " x" in the body of the first function with " lambda z . z * z "; then we 3replace the parameter" y" with" "to get:"  (lambda z . z * z) 3 ". Then execute the Beta Statute, there is " 3 * 3".

The formalization of the Beta rule is:

lambda x . B e = B[x := e] if free(e) subset free(B[x := e]) 

The last condition " if free(e) subset free(B[x := e])" explains why we need Alpha conversion: we can do the Beta protocol only if it does not cause any conflict between the bound identifier and the free identifier: if the identifier " z" is in " e" Is free, then we need to make sure that the Beta protocol will not cause " z" to become bound. If there is a naming conflict between Bthe variable bound in " e" and the free variable in " ", we need to use Alpha conversion to change the identifier name to make it different.

The example can make this clearer: suppose we have a function expression, "  lambda z . (lambda x . x + z) ", now, suppose we want to apply it:

(lambda z . (lambda x . x + z)) (x + 2) 

The parameter " (x + 2)" xis free. Now, suppose we don’t follow the rules and do the Beta statute directly. We will get:

lambda x . x + x + 2 

x + 2Variables that were previously free in " " are now bound. Suppose we apply the function again:

(lambda x . x + x + 2) 3 

Through the Beta protocol, we will get " 3 + 3 + 2".

What if we first adopt Alpha conversion the way it should be?

  • Made  alpha[x/y] are: (lambda z . (lambda y . y + z)) (x + 2)
  • Statute by Beta: (lambda y . y + x + 2) 3
  • Then the statute of 3 + x + 2 Beta: .

" 3 + x + 2" And " 3 + 3 + 2" are very different results!

The rules are pretty much that. There is another rule, you can optionally add a rule called Eta-Statute, but we will skip it. I describe here a Turing complete-a complete and effective computing system. To make it useful, or to see how it can be used to do something meaningful, we also need to define a bunch of basic functions that allow us to do mathematical calculations, conditional tests, recursion, etc., which I will discuss in the next article These ones.

We have not yet defined the Lambda-calculus model. (The original author discussed the concept of the model here and here .) The model is actually very important! Logicians came up with a complete model after fiddling with LC for several years. This is a very important thing, because although LC seems to be correct, in the early attempts to define a model for it, It failed. After all, remember that without an effective model, this means that the results of the system are meaningless!
 

Alonzo Church's genius work-numbers in lambda calculus


So, now, let's do something interesting with lambda calculus. First of all, for the sake of convenience, I will introduce some syntactic sugar to name functions so that we can read them when we encounter some complicated things.

Introduce the "global" function (that is, in all the introductions about lambda calculus that I have written, it can be used directly, instead of declaring this function in every expression), we will use the "let" expression :

let square = lambda x . x ^ 2 

This expression declares a function named "square", and its definition is lambda x . x ^ 2. If we have "  square 4", the equivalent expression of the above "let" expression is:

(lambda square . square 4) (lambda x . x ^ 2) 

In some examples, I used numbers and arithmetic operations. But numbers don't really exist in lambda calculus, we only have functions! Therefore, we need to invent some way of using functions to create numbers. Fortunately, Alonzo Church, the genius who invented the lambda calculus, figured out a way to do this. His functionalized version of numbers is called Church Numerals.

All Church numbers are functions with two parameters:

  • 0 is "  lambda s z . z ".
  • 1 is "  lambda s z . s z ".
  • 2 is " lambda s z . s (s z)
  • For any number " n", its Church number is a function of applying its first parameter to the second parameter" n"times.

A good way to understand is to use " z" as the name for the zero value, and " s" as the name of the subsequent function. Therefore, 0 is a function that only returns a value of "0"; 1 is the function that applies the successor function to 0 last time; 2 is the function that applies the successor function to the successor of zero, and so on.

Now that we are optimistic, if we want to do addition, x + ywe need to write a function with four parameters; two numbers that need to be added; and the " s" and " z" used when deriving numbers :

let add = lambda s z x y . x s (y s z) 

Let's currify it and see what's going on. First, it accepts two parameters, which are the two values ​​we need to add; second, it needs to normalize these two parameters so that they both use the binding of 0 ( z) and subsequent values ​​( s) set (i.e., the parameters are written s, and zin the form of a combination).

let add = lambda x y . (lambda s z . (x s (y s z))) 

Look at this formula, it says that in order to xand yadded first with the parameter " s" and " zcreate (regularized) Church number" " y." Then apply it xto the Church number y, this time use the Church number defined by " s" and " z" y. In other words, the result we get is a function that adds itself to another number. (To calculate x + y, to calculate  y is  z the successor to a few numbers, then the calculation x is  ythe successor to a few numbers.)

Let us take a closer look at 2 + 3the calculation process:

add (lambda s z . s (s z)) (lambda s z . s (s (s z))) news newz 

To make it easier to understand, do alpha transformation to the numbers 2 and 3. Replace "2" with "s2" and "z2", and replace 3 with "s3" and "z3":

add (lambda s2 z2 . s2 (s2 z2)) (lambda s3 z3 . s3 (s3 (s3 z3))) 

addReplace with the definition:

(lambda x y .(lambda s z. (x s y s z))) (lambda s2 z2 . s2 (s2 z2)) (lambda s3 z3 . s3 (s3 (s3 z3))) 

To adddo the beta statute:

lambda s z . (lambda s2 z2 . s2 (s2 z2)) s (lambda s3 z3 . s3 (s3 (s3 z3)) s z) 

Then the beta reduction of the Church number "3". This step is actually "regularization" 3: Replace the successor function and zero function in the definition of number 3 with the successor function and zero function in addthe parameter list:

lambda s z . (lambda s2 z2 . s2 (s2 z2)) s (s (s (s z))) 

Now, it's the most subtle step. Then do a beta reduction for the Church number "2". We know: 2 is a function, it accepts two parameters: a successor function and 0 (function). So, to add 2 and 3, we use the successor function to apply to the first parameter of 2; the operation result of 3 is applied to the second parameter (function 0)!

lambda s z . s (s (s (s (s z)))) 

So, our result is: Church number "5"!

 

Boolean value and choice in Lambda calculus


Now that we have introduced numbers in lambda calculus, we can express any calculation with only two things: one is how to express choice (branch), and the other is how to express repetition. In this article, I will discuss Boolean values ​​and selection, and the next article will introduce repetition and recursion.

We want to be able to write if / then / elseexpressions in the form of  sentences, just like we do in most programming languages. After representing numbers as functions like Church numbers, we also represent true and false values ​​as functions that perform an if-then-else operation on its parameters:

let TRUE = lambda x y . x 
let FALSE = lambda x y . y 

So, now we can write a " if" function, its first parameter is a conditional expression, the second parameter is an expression that is calculated if the condition is true, and the third parameter is if the condition is false The operation to be performed.

let IfThenElse = lambda cond true_expr false_expr . cond true_expr false_expr 

In addition, we also need to define common logical operations:

let BoolAnd = lambda x y . x y FALSE 
let BoolOr = lambda x y. x TRUE y 
let BoolNot = lambda x . x FALSE TRUE 

Now, let us go through these definitions. Let's take a look first BoolAnd:

  • BoolAnd TRUE FALSE, Expand TRUE and FALSE definitions:BoolAnd (lambda x y . x) (lambda x y . y)
  • Alpha transform true and false:BoolAnd (lambda xt yt . xt) (lambda xf yf . yf)
  • Now, expand BoolAnd:(lambda x y. x y FALSE) (lambda xt yt . xt) (lambda xf yf . yf)
  • Beta Statute:(lambda xt yt.xt) (lambda xf yf. yf) FALSE
  • Beta statute again:(lambda xf xf . yf)

So we get the BoolAnd TRUE FALSE = FALSEresult: . Let's take a look again BoolAnd FALSE TRUE:

  • BoolAnd (lambda x y . y) (lambda x y .x)
  • alpha transformation:BoolAnd (lambda xf yf . yf) (lambda xt yt . xt)
  • Expand BoolAnd: (lambda x y .x y FALSE) (lambda xf yf . yf) (lambda xt yt . xt)
  • Beta Statute:(lambda xf yf . yf) (lambda xt yt . xt) FALSE
  • Then beta statute:FALSE

and so,BoolAnd FALSE TRUE = FALSE

Finally, let us do the calculations BoolAnd TRUE TRUE:

  • Expand two TRUE: BoolAnd (lambda x y . x) (lambda x y . x)
  • alpha transformation: BoolAnd (lambda xa ya . xa) (lambda xb yb . xb)
  • Expand BoolAnd: (lambda x y . x y FALSE) (lambda xa ya . xa) (lambda xb yb . xb)
  • Beta Statute: (lambda xa ya . xa) (lambda xb yb . xb) FALSE
  • Beta Statute: (lambda xb yb .xb)

and so,BoolAnd TRUE TRUE = TRUE

Guess you like

Origin blog.csdn.net/smilejiasmile/article/details/107829363