[JS] generator版的阴阳谜题

1. 背景

阴阳谜题(yin yang puzzle),指的是以下Scheme代码实现的无限循环,

(let* [(yin ((lambda (foo) (newline) foo)
             (call/cc (lambda (bar) bar))))
       (yang ((lambda (foo) (display "*") foo)
              (call/cc (lambda (bar) bar))))]
  (yin yang))

输出结果为,

*
**
***
****
*****
******
*******
...

2. Python实现的阴阳谜题

今天在知乎看到了,
阴阳谜题的Python实现,就顺势把它翻译成了JS版。

def puzzle():
    def yin(yin):
        yield '@'
        def yang(yang):
            yield '*'
            yield from yin(yang)
        yield from yang(yang)
    yield from yin(yin)

for x, _ in zip(puzzle(), range(256)):
    print(x, end='')
print()

3. JavaScript

function* solution() {
    function* yin(_yin) {
        yield '@';

        function* yang(_yang) {
            yield '*';
            yield* _yin(_yang);
        }

        yield* yang(yang);
    }

    yield* yin(yin);
}

const iter = solution();
console.log(iter.next());  // @
console.log(iter.next());  // *
console.log(iter.next());  // @
console.log(iter.next());  // *
console.log(iter.next());  // *
console.log(iter.next());  // @
console.log(iter.next());  // *
console.log(iter.next());  // *
console.log(iter.next());  // *
console.log(iter.next());  // @

4. 原理分析

function* solution() {
    function* yin(_yin) {
        // 2. yin=yin1, _yin=yin1
        // 8. yin=yin1, _yin=yang1
        // 17. yin=yin1, _yin=yang2
        // 29. yin=yin1, _yin=yang3
        // 44. yin=yin1, _yin=yang4

        yield '@';  
        // 3. total output: @
        // 9. total output: @*@
        // 18. total output: @*@**@
        // 30. total output: @*@**@***@
        // 45. total output: @*@**@***@****@

        function* yang(_yang) {
            // 5. yang=yang1, _yang=yang1, (_yin=yin1)

            // 11. yang=yang2, _yang=yang2, (_yin=yang1)
            // 14. yang=yang1, _yang=yang2, (_yin=yin1)

            // 20. yang=yang3, _yang=yang3, (_yin=yang2)
            // 23. yang=yang2, _yang=yang3, (_yin=yang1)
            // 26. yang=yang1, _yang=yang3, (_yin=yin1)

            // 32. yang=yang4, _yang=yang4, (_yin=yang3)
            // 35. yang=yang3, _yang=yang4, (_yin=yang2)
            // 38. yang=yang2, _yang=yang4, (_yin=yang1)
            // 41. yang=yang1, _yang=yang4, (_yin=yin1)

            // 47. yang=yang5, _yang=yang5, (_yin=yang4)
            // 50. yang=yang4, _yang=yang5, (_yin=yang3)
            // 53. yang=yang3, _yang=yang5, (_yin=yang2)

            yield '*';  
            // 6. total output: @*
            // 12. total output: @*@*
            // 15. total output: @*@**
            // 21. total output: @*@**@*
            // 24. total output: @*@**@**
            // 27. total output: @*@**@***
            // 33. total output: @*@**@***@*
            // 36. total output: @*@**@***@**
            // 39. total output: @*@**@***@***
            // 42. total output: @*@**@***@****
            // 48. total output: @*@**@***@****@*
            // 51. total output: @*@**@***@****@**
            // 54. total output: @*@**@***@****@***

            yield* _yin(_yang);  
            // 7. _yin=yin1, _yang=yang1
            // 13. _yin=yang1, _yang=yang2
            // 16. _yin=yin1, _yang=yang2
            // 22. _yin=yang2, _yang=yang3
            // 25. _yin=yang1, _yang=yang3
            // 28. _yin=yin1, _yang=yang3
            // 34. _yin=yang3, _yang=yang4
            // 37. _yin=yang2, _yang=yang4
            // 40. _yin=yang1, _yang=yang4
            // 43. _yin=yin1, _yang=yang4
            // 49. _yin=yang4, _yang=yang5
            // 52. _yin=yang3, _yang=yang5
            // 55. _yin=yang2, _yang=yang5
        }

        yield* yang(yang);  
        // 4. yang=yang1
        // 10. yang=yang2
        // 19. yang=yang3
        // 31. yang=yang4
        // 46. yang=yang5
    }

    yield* yin(yin); 
    // 1. yin=yin1
}

以上代码中,我用序号标明了前55步的执行过程。

注:
(1)每次执行到function* yang(){}会创建一个新的generator。
以上用yang1yang2yang3yang4yang5,来标记所创建的不同generator。

(2)由于JS遵循词法作用域规则(闭包),
使用yield*调用一个generator,进入函数上下文后,
函数内部自由变量的绑定,是该generator被创建时,词法环境中的值。

例如,第4yield* yang(yang);,此时yang=yang1
于是第5步,yang=yang1, _yang=yang1
表示调用了yang1函数,参数_yang绑定为yang1

这时词法环境中_yin=yin1,所以,我们把它放到了后面的小括号中,写为,
5. yang=yang1, _yang=yang1, (_yin=yin1)

等到第13yield* yang(yang);,此时_yin=yang1, _yang=yang2
于是第14步,yang=yang1, _yang=yang2
表示再次调用了yang1函数,参数_yang绑定为yang2

这时词法环境中_yin的值,还是yang1创建时的_yin值,
我们找到了第5步的记录,5. yang=yang1, _yang=yang1, (_yin=yin1)
得知,_yin=yin1
因此,14. yang=yang1, _yang=yang2, (_yin=yin1)

(3)由于yang函数内部,_yin的词法绑定总是会发生变化,
因此,yang函数实际上相当于在进行循环执行。例如,

// 32. yang=yang4, _yang=yang4, (_yin=yang3)
// 35. yang=yang3, _yang=yang4, (_yin=yang2)
// 38. yang=yang2, _yang=yang4, (_yin=yang1)
// 41. yang=yang1, _yang=yang4, (_yin=yin1)

以上代码,相当于利用词法绑定,实现了循环操作。


参考

Python 实现阴阳谜题(Yin Yang Puzzle)

你可能感兴趣的:([JS] generator版的阴阳谜题)