[Programming Languages And Lambda calculi] 4.6 Lambda expression recursion (important, it is recommended to understand thoroughly)

4.6 Recursion

The exercises in the previous section require you to implement mult by implementing add. A similar realization reflects the information that numbers are encoded by functions.

Given the functions iszero, add and sub1, we can also implement mult without knowing how any value is encoded. We must define a recursive program to check whether the first parameter is 0, if not, add the second parameter to the recursive call, and decrement the first parameter.
Insert picture description here

The problem with defining the mult macro above is that it calls itself, so there is no way to expand mult into a pure lambda expression. Therefore, abbreviations are illegal.

4.6.1 Self-applied recursion

How does the multiplier function get its own handle? When the mult macro is defined, the definition of the multiplier function is temporarily unavailable, and the definition will be available later. In particular, when we call the multiplier function, we must have a handle to the multiplier function.

Therefore, instead of directly referencing itself, the multiplier function allows us to provide a multiple function t (itself) and the parameters for multiplication. To be more precise, with this strategy, the macro we define will no longer be a multiplier function, but a maker of a multiplier: it receives some function t and generates a multiplier function, this function will expect two Parameters and multiply them together:
Insert picture description here

The mkmult 0 macro is defined completely. And (mkmult 0 t) produces a multiplier function... when t is a multiplier function! Obviously, we still don't have a multiplier function so far. We try to parameterize the original mult definition itself, but after doing so, the mult definition will be lost.

Although we cannot provide t as a multiplier function, we can provide t as a multiplier function maker. Will this idea work, or will it cause an infinite loop backwards? The fact is that it can work as a maker.

Assuming that the maker is applied to the maker, a multiplier can be produced. Then, the initial parameter t applied to the manufacturer is the manufacturer. For the body part of the maker, wherever a multiplier is needed, we use (tt)-because t will be a maker, and applying a maker to itself will produce a multiplier. The following is a manufacturer mkmult 1 , which expects the parameters of a manufacturer:
Insert picture description here

If mkmulti 1 works, we can get the multiplier function by applying itself:
Insert picture description here

We try to apply this function to 0 and m (random m) and see if it returns 0. We will only expand macros when necessary, and we assume that iszero and 0 will be implemented correctly.
Insert picture description here

So far, everything is normal. If we multiply the non-zero n by m, what will we get?
Insert picture description here

Since mult = (mkmult 1 mkmult 1 ), the last step of the above process can also be written as (add m (mult (sub1 n) m))

therefore,
Insert picture description here

This is exactly the relationship between mult add sub1 0 that we need.

Exercise 4.11

Define a mksum macro to make (mksum mksum) behave like a summation function. It will consume a number n and add all natural numbers from 0 to n to return.

answer

mksum ≐ λt.λn if (iszero n) 0 (add ((t t) (sub1 n)) n)

4.6.2 Pop-up from self application

The method in the previous section allows us to define any recursive function we want. However, this is a bit clumsy, because we have to define a manufacturing function that recursively applies an initial parameter to itself. For simplicity, we need to pull the self-applied pattern into its own abstract definition.

More specifically, we need a function, called mk, which can receive any manufacturer like mkmult 0 and produce the function. For example, (mk mkmult 0 ) will produce a multiplier.
Insert picture description here

The above definition of mk is flawed, but we can start from this poor definition. The mk function needs to receive a producer t and produce an operation function. That is, calling (mk t) creates an operation function.

We can use the method in the previous section to repair the mk defined by the loop:
Insert picture description here

Next, let us verify whether the behavior of (mk mkmult 0 ) is consistent with mult:
Insert picture description here

4.6.3 Fixed-point and Y combinators

The mk function may seem a bit mysterious now. Somehow, it makes mkmult 0 useful, even though mkmult 0 is set so that it can only produce a multiplier after passing the multiplier to it!

Generally speaking, mkmult 0 may receive any function, and the resulting "multiplier" will have a variety of different behaviors, and the output function will usually be very different from the input function. But mk managed to choose an input function for mkmult 0 so that the output function remains the same as the input function. In other words, mk will find the fixed point of mkmult 0 .

It turns out that mk can find the fixed point of any function. In other words, if applying mk to M produces N, then applying M to N will produce the same N again.

Theorem 4.1 For any M , M (mk M ) = n (mk M )

Proof of Theorem 4.1 : Since = n is a symmetric closure of →→ n , we can prove the theorem indirectly by proving M →→ n M (mk M):
Insert picture description here

Mk a similar behavior of functions are called designated operator . The mk function is just one of them. The more famous fixed-point operator is called Y:
Insert picture description here

Generally speaking, Y allows us to define recursive functions more conveniently. For example, we can define sum like this :
Insert picture description here

Since we do not need to repeat a producer expression, we can skip the intermediate producer mksum and directly apply Y to the producer function.

In addition, the programmer who sees the definition of sum above will immediately notice Y, see that s is the parameter of the parameter of Y, and then find λn... as the definition of the recursive function s.

Exercise 4.12

Prove that for any M , M ( Y M ) = n ( Y M )

answer

(Y M)
= ((λf.(λx.f (x x)) (λx.f (x x))) M)
nβ (λx.M (x x)) (λx.M (x x))
nβ (M ((λx.M (x x)) (λx.M (x x))))

M (Y M)
= M ((λf.(λx.f (x x)) (λx.f (x x))) M)
nβ (M ((λx.M (x x)) (λx.M (x x))))

Left = right, so it is true.

Exercise 4.13

Defines the encoding of Lisp address pairs, which consists of the following macros:

  • null, a constant
  • cons, two parameters, return an address pair
  • car, if the parameter is null, return true; if the parameter is an address pair, return false
  • cdr, receives the address pair as a parameter and returns the second element of the address pair.

Your code must satisfy the following equation:
Insert picture description here
Your code does not need to specify any specific meaning for the expression, such as (car null) or (car cons)

answer
  • null ≐ ⟨true, false
  • consλM.λNfalse, ⟨M, N⟩⟩
  • isnullλM.fst M
  • carλM.fst (snd M)
  • cdrλM.snd (snd M)

Exercise 4.14

Using the code you completed in the previous exercise, define length, which receives a list of boolean values ​​and returns the address pairs in the list. The list of boolean values ​​is either null or (cons bl), where b is a boolean value and l is a list of boolean values.

answer

lengthY (λf.λM.if (isnull M) 0 (add1 (f (cdr M))))

Guess you like

Origin blog.csdn.net/qq_35714301/article/details/113838885