An interesting solution of yin-yang puzzle, and create the universe (笑)

1
2
3
4
5
6
7
8
9
10
11
12
def yin(cb1):
yield '@'
def yang(cb2):
yield '*'
yield from cb1(cb2)
yield from yang(yang)

def puzzle():
yield from yin(yin)

for x, _ in zip(puzzle(), range(50)):
print(x, end='')
1
@*@**@***@****@*****@******@*******@********@*****

Each time the prgram will output an extra * symbol, therefore our universe is created.

This puzzle is a famous one in functional programming area (probably?). Notably solved by utilizing call/cc (continuation).

As we all know, python’s generator is no more than a simplified version of continuation. And that’s where the solution comes from.

An interesting question here is why there’s an increment on * symbol.

The key to understanding this is noticing the context carried with each generator. To make everything clear, we use notation like yin_yang~yin. The name befoere _ indicates the generator name we are currently calling, the sequence after _ indicates the value of cb1 in the nested context, from the nearest to the furthest.

Here we only need to care about cb1 (callback 1), since it is the place where magic happens. For cb2, since we only call yang(yang), it just duplicates itself every time.

Then we can interpret the process in the following way:

1
2
3
4
5
6
7
8
yin(yin) \\ @
-> yang_yin(yang_yin) \\ *
-> yin(yang_yin) \\ @
-> yang_yang~yin(yang_yang~yin) \\ *
-> yang_yin(yang_yang~yin) \\ *
-> yin(yang_yang~yin) \\ @
-> yang_yang~yang~yin(yang_yang~yang~yin)
...

We can see how the context grows. The key here is, even though we are always passing yang to cb1, the context carried with each yang is different.

If this explaination is not clear enough for you, a comprehensive scheme version explaination is available here.