Article Directory
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.
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:
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:
If mkmulti 1 works, we can get the multiplier function by applying itself:
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.
So far, everything is normal. If we multiply the non-zero n by m, what will we get?
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,
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.
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:
Next, let us verify whether the behavior of (mk mkmult 0 ) is consistent with mult:
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):
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:
Generally speaking, Y allows us to define recursive functions more conveniently. For example, we can define sum like this :
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:
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.λN ⟨false
, ⟨M, N⟩⟩isnull
≐ λM.fst
Mcar
≐ λ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
length
≐ Y
(λf.λM.if
(isnull
M) 0 (add1
(f (cdr
M))))