[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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352