Document directory
- "Yin and yang puzzles" in Scheme"
"Yin and yang puzzles" in Scheme"
In scheme, there is a famous "yin-yang Puzzle", which is probably a few lines of code:
(let* ((yin ((lambda (foo) (newline) foo) (call/cc (lambda (bar) bar)))) (yang ((lambda (foo) (write-char #/*) foo) (call/cc (lambda (bar) bar))))) (yin yang))
Although the program is short, when I saw it for the first time, I couldn't guess its running result. On the surface, I know that call/CC will bring the program into an endless loop, but it is not clear what logic is inside the loop. I was shocked to get the program to run in the scheme environment. The result was as follows:
****************************...
Then, I took a whole day to figure out the ins and outs of the "yin and yang Puzzle. There are a lot of people talking about this puzzle on the Internet, but there are few people who detail the puzzle. I will simply write down my understanding of this question, which is not necessarily correct and only for your reference.
The first thing to understand is what structure the "yin and yang Puzzle" program is. We can see from the inside out:
(call/cc (lambda (bar) bar))
This sentence transfers the current continuation to the lambda bar, while the latter simply returns the passed parameters. That is to say, the function of this sentence is to get the current progress. The following combination
((lambda (foo) (newline) foo) (call/cc (lambda (bar) bar)))
It means to pass the current request as a parameter to the anonymous process (lambda (FOO) (newline) Foo), while the latter outputs a line break and then simply returns the passed parameter. Further:
(yin ((lambda (foo) (newline) foo) (call/cc (lambda (bar) bar))))
It is used to bind the local variable yin to call/CC after the line break is output. The binding to Yang is similar. And the following sentence
(yin yang)
Yin is called with Yang as the parameter in the current yin and yang binding environment. In scheme, the so-called continuation is a process that can be called or play the role of a parameter. Therefore, the syntax such as (Yin Yang) has no problem. The key is what results will be produced by calls like (Yin Yang, this is not something I can see at a glance (maybe I am too dull, and most smart people can quickly find the answer ).
Forget it. If you cannot see it at a glance, put the strange code like yin yang aside. After sorting out the ideas, the entire "Yin and Yang puzzle" actually did the following:
① Start execution (let *) structure ② get current continue ③ output line break ④ assign the obtained result to Yin ⑤ get current continue 6 Output asterisk 7 grant the obtained result to YANG Jing with Yang as the parameter to call Yin
It seems that there are eight steps like this, but in order to thoroughly understand the running logic, you must determine two things:
1. What structure is (let?
I don't need to answer this question. scheme talks a lot in books and materials. Let's only talk about one of the most important ones: (let *) there is a list of variables, such as yin and yang here. (Let *) first creates the binding of the first variable, then, in the environment where the first variable is bound to a known environment, the binding of the second variable is created and pushed accordingly until all bindings are created, obtain the value of the subject expression in the Environment bound to all these variables. In this example, the subject expression is a simple sentence: (Yin Yang)
2. What is the result of step 2 and Step 5?
If you do not know what to continue or how to use it, you are advised to check the document first. I only want to emphasize that the continuation of call/CC is the "all future" of the current expression of call/CC ". The word "all future" is copied from scheme's standard document r5rs. It means that when there is no call/CC, what should we do after obtaining the value of the expression, and what will be done when the call/CC result process is called directly.
That is to say, if ② is not a call/CC but a normal expression, we will do these things after obtaining the value of the expression: Output A line break and assign the value of this expression to Yin, create a new environment that contains Yin, and complete the subsequent steps in the new environment-I will remember this as ().
Now, the Code obtains the current continuation in section ②. This means that, once we call this operation in the future, the call will repeat the same steps, and then the "expression value" is used ", it is the parameter that we pass in when the call continues.
Similarly, if the call result is obtained at Step 5, the following operations will be performed: output an asterisk, assign the expression value to Yang, and create a new environment containing Yang, then complete the subsequent steps in the new environment-I will record this as (B ).
Now, after figuring out the two questions, we can try to manually run the "yin and yang Puzzle. In the following analysis, we refer to what we get at ② as Ca and what we get at ⑤ as CB:
1. Get the CA to continue, output the line break, and assign the CA to Yin. I will write down these steps as follows:
/yin = Ca(_)
Where,/indicates the output line break, and Ca (_) indicates the continuation obtained at ②. Before giving the continuation to Yin, the value of Yin is undefined _
2. In an environment containing Yin, continue CB is obtained. An asterisk is output, and CB is assigned to Yang as follows:
*yang = Cb( Ca(_) )
Where CB (CA (_) indicates the continuation obtained at ⑤. Before the continuation is granted to Yang, the value of Yin is Ca (_)
3. Call (Yin Yang ). From the analysis just now, we can know that what we need to do with this call is to take the CB (CA (_) bound by Yang as the parameter, call the CA (_) bound to Yin (_). Apply the sentence (a) just analyzed: Output A line break, assign the CB (CA (_) value to Yin, and create a new environment containing Yin, then, complete the subsequent steps in the new environment. Note
/yin = Cb( Ca(_) )
4. Now we need to "complete the subsequent steps. The subsequent step is ⑤, so the next step should be:
*yang = Cb( Cb( Ca(_) ) )
5. Yin Yang again. The binding process in Yin and Yang is different from that in the previous one. Most importantly, CB (CA (_) instead of Ca (...) is bound to Yin (...), this indicates that the call to Yin will be described by (B), not by (. Now, Yin Yang means that the asterisk is output, and CB (CA (_) is assigned to Yang to create a new environment containing Yang, then, complete the subsequent steps in the new environment. Note
*yang = Cb( Cb( Ca(_) ) )
One question is, what is Yin now? Note: before the call, the Yin value is CB (CA (_). The note method I invented indicates the environment when this continuation is created, that is, when this continuation is obtained, the yin value is Ca (_). Now we call Yin. In a sense, this is equivalent to returning to the environment when we create this continuation. We can simply think that after the call, the value of Yin is changed back to Ca (_). Note (Here we only provide the "Approximation" statement, and we will discuss why the Yin value changes back later ):
yin = Ca(_)
6. This is the next round of yin yang. Because the value of Yin is Ca (_), we will return to the things described in (, note:
/yin = Cb( Cb( Ca(_) ) )
7. "Run" like this and write a few more steps:
*yang = Cb( Cb( Cb( Ca(_) ) ) )*yang = Cb( Cb( Cb( Ca(_) ) ) )yin = Cb( Ca(_) )*yang = Cb( Cb( Cb( Ca(_) ) ) )yin = Ca(_)/yin = Cb( Cb( Cb( Ca(_) ) ) )... ...
Connect the output obtained in the above seven steps:
/*/**/***/...
Isn't this the result of the "Yin and Yang puzzle? It seems that things are still going smoothly, but we still have a problem to solve: When the Yin value is CB (...) in the (Yin Yang) Call, why does the Yin value change back? This problem is related to the environment model used by scheme. It is recommended that you think back to chapter 3rd in the construction and interpretation of computer programs. I will explain the environment changes in the "Yin and Yang Puzzle", as shown in the following two figures:
1. Before entering (let *), there is only one initial environment. Bind the CA to Yin and create an environment containing Yin. This is equivalent to creating a sub-environment containing Yin. A pointer in the sub-Environment points to the initial environment. Is the green Yin in the left graph. The solid arrow indicates that the parent environment is referenced, and the dotted arrow indicates that the variable is bound.
2. In the newly created sub-environment, bind CB to Yang and create a new sub-environment containing Yang. In the new sub-environment, a pointer points to the sub-environment containing Yin. This is the green Yang in the left figure.
3. When calling (Yin Yang), because the CA process bound to Yin indicates re-creating the environment containing Yin, the blue Yin is displayed on the left, however, Yin binding points to the green CB. Next, create a blue Yang, which points to a new CB. Now we use blue yin and yang.
4. Next (Yin Yang) call, because Yin binding points to the green CB, it means that the environment containing Yang is re-created in the green Yin environment. As a result, green Yang is replaced by the newly created red Yang, and the red Yang binding points to the blue CB. The red Yang's parent environment is also consistent with green Yang, which is a green Yin. This is the way in the right image. Now we use green Yin and red Yang. That is to say, we are still using Yin pointing to CB, and now we have recovered to using green Yin. This is why the Yin value changes back. At the same time, because the Red Yang points to the blue CB, the next Yin will change back to the blue Yin, and the next time will change back to the green Yin. Therefore, each line of the yin and yang puzzle outputs an asterisk more than the previous line.
This is the answer to the "yin and yang Puzzle" I have deduced (the above explanation is just a conceptual model, which is not exactly the same as the implementation of scheme ). However, the answer is a manual reasoning. Can it be automatically proved by the program? It should be okay. I expanded the "Yin and Yang puzzle" so that the program could automatically track the meaning of each continuation and print the output automatically. The modified code is as follows:
(define cc-dict '())(define (insert-cc! cc flag) (if (assq cc cc-dict) #f (set! cc-dict (cons (cons cc (cons flag (length cc-dict))) cc-dict))))(define (display-cc cc prefix) (display prefix) (display #/() ((lambda (cc-pair) (cond (cc-pair (display (cadr cc-pair)) (display #/,) (display (cddr cc-pair))))) (assq cc cc-dict)) (display #/)))(let ((count 5) (yang #f)) (call/cc (lambda (exit) (let* ((yin ((lambda (foo) (write-char #//) (newline) (insert-cc! foo #/a) (display-cc foo "yin") (display-cc yang "yang") (set! count (- count 1)) (if (= 0 count) (exit 'end) foo)) (call/cc (lambda (bar) bar)))) (yang ((lambda (foo) (write-char #/*) (insert-cc! foo #/b) (display-cc yin "yin") (display-cc foo "yang") foo) (call/cc (lambda (bar) bar))))) (yin yang)))))
The code above shows the running process of the first five lines of the "Yin and Yang puzzle". Before each value is assigned to Yin and Yang, the Code prints the content of yin and yang, the print format is Yin (A, 0) Yang (B, 1) or similar. in this format, Yin (A, 0) indicates that the value of Yin is continue ca, this continues as the 1st progress (based on the 0 index) generated by the program ). The running result of the above Code is:
/yin(a,0)yang()*yin(a,0)yang(b,1)/yin(b,1)yang()*yin(b,1)yang(b,2)*yin(a,0)yang(b,2)/yin(b,2)yang()*yin(b,2)yang(b,3)*yin(b,1)yang(b,3)*yin(a,0)yang(b,3)/yin(b,3)yang()*yin(b,3)yang(b,4)*yin(b,2)yang(b,4)*yin(b,1)yang(b,4)*yin(a,0)yang(b,4)/yin(b,4)yang()
From this running result, we can clearly see the changes of yin and yang, or how Yin changes back to the original values step by step in each row, and finally changed back to Ca to output line breaks.