[JS]|[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=yang4yield '@'; // 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。
【[JS]|[JS] 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)

    推荐阅读