Lably
Skip to content

Call-with-current-continuation

Control flow operator in functional programming

  • 11 min read
  • 4 chapters

Related on Lably

Through Scheme (programming language), Computer programming and Function (computer programming)

Taking a function f as its only argument, (call/cc f) within an expression is applied to the current continuation of the expression. For example ((call/cc f) e2) is equivalent to applying f to the current continuation of the expression. The current continuation is given by replacing (call/cc f) by a variable c bound by a lambda abstraction, so the current continuation is (lambda (c) (c e2)). Applying the function f to it gives the final result (f (lambda (c) (c e2))).

As a complementary example, in an expression (e1 (call/cc f)), the continuation for the sub-expression (call/cc f) is (lambda (c) (e1 c)), so the whole expression is equivalent to (f (lambda (c) (e1 c))). In other words, it takes a "snapshot" of the current control context or control state of the program as an object and applies f to it. The continuation object is a first-class value and is represented as a function, with function application as its only operation. When a continuation object is applied to an argument, the existing continuation is eliminated and the applied continuation is restored in its place, so that the program flow will continue at the point at which the continuation was captured and the argument of the continuation then becomes the "return value" of the call/cc invocation. Continuations created with call/cc may be called more than once, and even from outside the dynamic extent of the call/cc application.

In computer science, making this type of implicit program state visible as an object is termed reification. (Scheme does not syntactically distinguish between applying continuations or functions.)

With call/cc a variety of complex control operators can be implemented from other languages via a few lines of code, e.g., McCarthy's amb operator for nondeterministic choice, Prolog-style backtracking, Simula 67-style coroutines and generalizations thereof, Icon-style generators, or engines and threads or even the obscure COMEFROM.

Examples

As shown by the next example, call/cc can be used to emulate the function of the return statement known from C-style languages, which is missing from Scheme:

<span id="mwPA"></span><span class="p" id="mwPQ">(</span><span class="k" id="mwPg">define</span><span class="w" id="mwPw"> </span><span class="p" id="mwQA">(</span><span class="nf" id="mwQQ">f</span><span class="w" id="mwQg"> </span><span class="nv" id="mwQw">return</span><span class="p" id="mwRA">)</span><span class="w" id="mwRQ"> </span> <span class="w" id="mwRg"> </span><span class="p" id="mwRw">(</span><span class="nf" id="mwSA">return</span><span class="w" id="mwSQ"> </span><span class="mi" id="mwSg">2</span><span class="p" id="mwSw">)</span><span class="w" id="mwTA"> </span> <span class="w" id="mwTQ"> </span><span class="mi" id="mwTg">3</span><span class="p" id="mwTw">)</span> <span class="p" id="mwUA">(</span><span class="nf" id="mwUQ">f</span><span class="w" id="mwUg"> </span><span class="p" id="mwUw">(</span><span class="k" id="mwVA">lambda</span><span class="w" id="mwVQ"> </span><span class="p" id="mwVg">(</span><span class="nf" id="mwVw">x</span><span class="p" id="mwWA">)</span><span class="w" id="mwWQ"> </span><span class="nv" id="mwWg">x</span><span class="p" id="mwWw">))</span> <span class="k" id="mwXA">=></span><span class="w" id="mwXQ"> </span><span class="mi" id="mwXg">3</span> <span class="p" id="mwXw">(</span><span class="nb" id="mwYA">call-with-current-continuation</span><span class="w" id="mwYQ"> </span><span class="nv" id="mwYg">f</span><span class="p" id="mwYw">)</span> <span class="k" id="mwZA">=></span><span class="w" id="mwZQ"> </span><span class="mi" id="mwZg">2</span>

Calling f with a regular function argument first applies this function to the value 2, then returns 3. However, when f is passed to call/cc (as in the last line of the example), applying the parameter (the continuation) to 2 forces execution of the program to jump to the point where call/cc was called, and causes call/cc to return the value 2. This is then printed by the display function.

In the next example, call/cc is used twice: once to generate a "return" continuation as in the first example and once to suspend an iteration through a list of items:

<span id="mwbQ"></span><span class="c1" id="mwbg">;; [LISTOF X] -> ( -> X u 'you-fell-off-the-end)</span> <span class="p" id="mwbw">(</span><span class="k" id="mwcA">define</span><span class="w" id="mwcQ"> </span><span class="p" id="mwcg">(</span><span class="nf" id="mwcw">generate-one-element-at-a-time</span><span class="w" id="mwdA"> </span><span class="nv" id="mwdQ">lst</span><span class="p" id="mwdg">)</span> <span class="w" id="mwdw"> </span><span class="c1" id="mweA">;; Both internal functions are closures over lst</span> <span class="w" id="mweQ"> </span><span class="c1" id="mweg">;; Internal variable/Function which passes the current element in a list</span> <span class="w" id="mwew"> </span><span class="c1" id="mwfA">;; to its return argument (which is a continuation), or passes an end-of-list marker </span> <span class="w" id="mwfQ"> </span><span class="c1" id="mwfg">;; if no more elements are left. On each step the function name is </span> <span class="w" id="mwfw"> </span><span class="c1" id="mwgA">;; rebound to a continuation which points back into the function body,</span> <span class="w" id="mwgQ"> </span><span class="c1" id="mwgg">;; while return is rebound to whatever continuation the caller specifies.</span> <span class="w" id="mwgw"> </span><span class="p" id="mwhA">(</span><span class="k" id="mwhQ">define</span><span class="w" id="mwhg"> </span><span class="p" id="mwhw">(</span><span class="nf" id="mwiA">control-state</span><span class="w" id="mwiQ"> </span><span class="nv" id="mwig">return</span><span class="p" id="mwiw">)</span> <span class="w" id="mwjA"> </span><span class="p" id="mwjQ">(</span><span class="nb" id="mwjg">for-each</span><span class="w" id="mwjw"> </span> <span class="w" id="mwkA"> </span><span class="p" id="mwkQ">(</span><span class="k" id="mwkg">lambda</span><span class="w" id="mwkw"> </span><span class="p" id="mwlA">(</span><span class="nf" id="mwlQ">element</span><span class="p" id="mwlg">)</span> <span class="w" id="mwlw"> </span><span class="p" id="mwmA">(</span><span class="k" id="mwmQ">set!</span><span class="w" id="mwmg"> </span><span class="nv" id="mwmw">return</span><span class="w" id="mwnA"> </span><span class="p" id="mwnQ">(</span><span class="nb" id="mwng">call-with-current-continuation</span> <span class="w" id="mwnw"> </span><span class="p" id="mwoA">(</span><span class="k" id="mwoQ">lambda</span><span class="w" id="mwog"> </span><span class="p" id="mwow">(</span><span class="nf" id="mwpA">resume-here</span><span class="p" id="mwpQ">)</span> <span class="w" id="mwpg"> </span><span class="c1" id="mwpw">;; Grab the current continuation</span> <span class="w" id="mwqA"> </span><span class="p" id="mwqQ">(</span><span class="k" id="mwqg">set!</span><span class="w" id="mwqw"> </span><span class="nv" id="mwrA">control-state</span><span class="w" id="mwrQ"> </span><span class="nv" id="mwrg">resume-here</span><span class="p" id="mwrw">)</span> <span class="w" id="mwsA"> </span><span class="p" id="mwsQ">(</span><span class="nf" id="mwsg">return</span><span class="w" id="mwsw"> </span><span class="nv" id="mwtA">element</span><span class="p" id="mwtQ">)))))</span><span class="w" id="mwtg"> </span><span class="c1" id="mwtw">;; (return element) evaluates to next return</span> <span class="w" id="mwuA"> </span><span class="nv" id="mwuQ">lst</span><span class="p" id="mwug">)</span> <span class="w" id="mwuw"> </span><span class="p" id="mwvA">(</span><span class="nf" id="mwvQ">return</span><span class="w" id="mwvg"> </span><span class="ss" id="mwvw">'you-fell-off-the-end</span><span class="p" id="mwwA">))</span> <span class="w" id="mwwQ"> </span> <span class="w" id="mwwg"> </span><span class="c1" id="mwww">;; (-> X u 'you-fell-off-the-end)</span> <span class="w" id="mwxA"> </span><span class="c1" id="mwxQ">;; This is the actual generator, producing one item from a-list at a time.</span> <span class="w" id="mwxg"> </span><span class="p" id="mwxw">(</span><span class="k" id="mwyA">define</span><span class="w" id="mwyQ"> </span><span class="p" id="mwyg">(</span><span class="nf" id="mwyw">generator</span><span class="p" id="mwzA">)</span> <span class="w" id="mwzQ"> </span><span class="p" id="mwzg">(</span><span class="nb" id="mwzw">call-with-current-continuation</span><span class="w" id="mw0A"> </span><span class="nv" id="mw0Q">control-state</span><span class="p" id="mw0g">))</span> <span class="w" id="mw0w"> </span><span class="c1" id="mw1A">;; Return the generator </span> <span class="w" id="mw1Q"> </span><span class="nv" id="mw1g">generator</span><span class="p" id="mw1w">)</span> <span class="p" id="mw2A">(</span><span class="k" id="mw2Q">define</span><span class="w" id="mw2g"> </span><span class="nv" id="mw2w">generate-digit</span> <span class="w" id="mw3A"> </span><span class="p" id="mw3Q">(</span><span class="nf" id="mw3g">generate-one-element-at-a-time</span><span class="w" id="mw3w"> </span><span class="o" id="mw4A">'</span><span class="p" id="mw4Q">(</span><span class="mi" id="mw4g">0</span><span class="w" id="mw4w"> </span><span class="mi" id="mw5A">1</span><span class="w" id="mw5Q"> </span><span class="mi" id="mw5g">2</span><span class="p" id="mw5w">)))</span> <span class="p" id="mw6A">(</span><span class="nf" id="mw6Q">generate-digit</span><span class="p" id="mw6g">)</span><span class="w" id="mw6w"> </span><span class="c1" id="mw7A">;; 0</span> <span class="p" id="mw7Q">(</span><span class="nf" id="mw7g">generate-digit</span><span class="p" id="mw7w">)</span><span class="w" id="mw8A"> </span><span class="c1" id="mw8Q">;; 1</span> <span class="p" id="mw8g">(</span><span class="nf" id="mw8w">generate-digit</span><span class="p" id="mw9A">)</span><span class="w" id="mw9Q"> </span><span class="c1" id="mw9g">;; 2</span> <span class="p" id="mw9w">(</span><span class="nf" id="mw-A">generate-digit</span><span class="p" id="mw-Q">)</span><span class="w" id="mw-g"> </span><span class="c1" id="mw-w">;; you-fell-off-the-end</span>

Every time the loop is about to process another item from the list, the function grabs the current continuation, and assigns it to the variable 'control-state'. This variable initially is the closure that iterates through all elements of the list. As the computation progresses, it becomes a closure that iterates through a suffix of the given list. While the use of "call/cc" is unnecessary for a linear collection, such as [LISTOF X], the code generalizes to any collection that can be traversed.

Call-with-current-continuation can also express other sophisticated primitives. For example, the next sample performs cooperative multitasking using continuations:

<span id="mwAQM"></span><span class="c1" id="mwAQQ">;; Cooperative multitasking using call-with-current-continuation</span> <span class="c1" id="mwAQU">;; in 25 lines of scheme</span> <span class="c1" id="mwAQY">;; The list of threads waiting to run. This is a list of one</span> <span class="c1" id="mwAQc">;; argument non-returning functions (continuations, mostly)</span> <span class="c1" id="mwAQg">;; A continuation is a non-returning function, just like (exit),</span> <span class="c1" id="mwAQk">;; in that it never gives up control to whatever called it.</span> <span class="p" id="mwAQo">(</span><span class="k" id="mwAQs">define</span><span class="w" id="mwAQw"> </span><span class="nv" id="mwAQ0">ready-list</span><span class="w" id="mwAQ4"> </span><span class="o" id="mwAQ8">'</span><span class="p" id="mwARA">())</span> <span class="c1" id="mwARE">;; A non-returning function. If there is any other thread</span> <span class="c1" id="mwARI">;; waiting to be run, it causes the next thread to run if there</span> <span class="c1" id="mwARM">;; is any left to run, otherwise it calls the original exit</span> <span class="c1" id="mwARQ">;; which exits the whole environment.</span> <span class="p" id="mwARU">(</span><span class="k" id="mwARY">define</span><span class="w" id="mwARc"> </span><span class="nb" id="mwARg">exit</span> <span class="w" id="mwARk"> </span><span class="c1" id="mwARo">;; The original exit which we override.</span> <span class="w" id="mwARs"> </span><span class="p" id="mwARw">(</span><span class="k" id="mwAR0">let</span><span class="w" id="mwAR4"> </span><span class="p" id="mwAR8">((</span><span class="nb" id="mwASA">exit</span><span class="w" id="mwASE"> </span><span class="nb" id="mwASI">exit</span><span class="p" id="mwASM">))</span> <span class="w" id="mwASQ"> </span><span class="c1" id="mwASU">;; The overriding function.</span> <span class="w" id="mwASY"> </span><span class="p" id="mwASc">(</span><span class="k" id="mwASg">lambda</span><span class="w" id="mwASk"> </span><span class="p" id="mwASo">()</span> <span class="w" id="mwASs"> </span><span class="p" id="mwASw">(</span><span class="k" id="mwAS0">if</span><span class="w" id="mwAS4"> </span><span class="p" id="mwAS8">(</span><span class="nb" id="mwATA">not</span><span class="w" id="mwATE"> </span><span class="p" id="mwATI">(</span><span class="nb" id="mwATM">null?</span><span class="w" id="mwATQ"> </span><span class="nv" id="mwATU">ready-list</span><span class="p" id="mwATY">))</span> <span class="w" id="mwATc"> </span><span class="c1" id="mwATg">;; There is another thread waiting to be run.</span> <span class="w" id="mwATk"> </span><span class="c1" id="mwATo">;; So we run it.</span> <span class="w" id="mwATs"> </span><span class="p" id="mwATw">(</span><span class="k" id="mwAT0">let</span><span class="w" id="mwAT4"> </span><span class="p" id="mwAT8">((</span><span class="nf" id="mwAUA">cont</span><span class="w" id="mwAUE"> </span><span class="p" id="mwAUI">(</span><span class="nb" id="mwAUM">car</span><span class="w" id="mwAUQ"> </span><span class="nv" id="mwAUU">ready-list</span><span class="p" id="mwAUY">)))</span> <span class="w" id="mwAUc"> </span><span class="p" id="mwAUg">(</span><span class="k" id="mwAUk">set!</span><span class="w" id="mwAUo"> </span><span class="nv" id="mwAUs">ready-list</span><span class="w" id="mwAUw"> </span><span class="p" id="mwAU0">(</span><span class="nb" id="mwAU4">cdr</span><span class="w" id="mwAU8"> </span><span class="nv" id="mwAVA">ready-list</span><span class="p" id="mwAVE">))</span> <span class="w" id="mwAVI"> </span><span class="c1" id="mwAVM">;; Since the ready-list is only non-returning</span> <span class="w" id="mwAVQ"> </span><span class="c1" id="mwAVU">;; functions, this will not return.</span> <span class="w" id="mwAVY"> </span><span class="p" id="mwAVc">(</span><span class="nf" id="mwAVg">cont</span><span class="w" id="mwAVk"> </span><span class="no" id="mwAVo">#f</span><span class="p" id="mwAVs">))</span> <span class="w" id="mwAVw"> </span><span class="c1" id="mwAV0">;; Nothing left to run.</span> <span class="w" id="mwAV4"> </span><span class="c1" id="mwAV8">;; The original (exit) is a non returning function,</span> <span class="w" id="mwAWA"> </span><span class="c1" id="mwAWE">;; so this is a non-returning function.</span> <span class="w" id="mwAWI"> </span><span class="p" id="mwAWM">(</span><span class="nb" id="mwAWQ">exit</span><span class="p" id="mwAWU">)))))</span> <span class="c1" id="mwAWY">;; Takes a one argument function with a given</span> <span class="c1" id="mwAWc">;; argument and forks it off. The forked function's new</span> <span class="c1" id="mwAWg">;; thread will exit if/when the function ever exits.</span> <span class="p" id="mwAWk">(</span><span class="k" id="mwAWo">define</span><span class="w" id="mwAWs"> </span><span class="p" id="mwAWw">(</span><span class="nf" id="mwAW0">fork</span><span class="w" id="mwAW4"> </span><span class="nv" id="mwAW8">fn</span><span class="w" id="mwAXA"> </span><span class="nv" id="mwAXE">arg</span><span class="p" id="mwAXI">)</span> <span class="w" id="mwAXM"> </span><span class="p" id="mwAXQ">(</span><span class="k" id="mwAXU">set!</span><span class="w" id="mwAXY"> </span><span class="nv" id="mwAXc">ready-list</span> <span class="w" id="mwAXg"> </span><span class="p" id="mwAXk">(</span><span class="nb" id="mwAXo">append</span><span class="w" id="mwAXs"> </span><span class="nv" id="mwAXw">ready-list</span> <span class="w" id="mwAX0"> </span><span class="c1" id="mwAX4">;; This function added to the </span> <span class="w" id="mwAX8"> </span><span class="c1" id="mwAYA">;; ready-list is non-returning,</span> <span class="w" id="mwAYE"> </span><span class="c1" id="mwAYI">;; since exit is non returning.</span> <span class="w" id="mwAYM"> </span><span class="p" id="mwAYQ">(</span><span class="nb" id="mwAYU">list</span> <span class="w" id="mwAYY"> </span><span class="p" id="mwAYc">(</span><span class="k" id="mwAYg">lambda</span><span class="w" id="mwAYk"> </span><span class="p" id="mwAYo">(</span><span class="nf" id="mwAYs">x</span><span class="p" id="mwAYw">)</span> <span class="w" id="mwAY0"> </span><span class="p" id="mwAY4">(</span><span class="nf" id="mwAY8">fn</span><span class="w" id="mwAZA"> </span><span class="nv" id="mwAZE">arg</span><span class="p" id="mwAZI">)</span> <span class="w" id="mwAZM"> </span><span class="p" id="mwAZQ">(</span><span class="nb" id="mwAZU">exit</span><span class="p" id="mwAZY">))))))</span> <span class="c1" id="mwAZc">;; Gives up control for the next thread waiting to be run.</span> <span class="c1" id="mwAZg">;; Although it will eventually return, it gives up control</span> <span class="c1" id="mwAZk">;; and will only regain it when the continuation is called.</span> <span class="p" id="mwAZo">(</span><span class="k" id="mwAZs">define</span><span class="w" id="mwAZw"> </span><span class="p" id="mwAZ0">(</span><span class="nf" id="mwAZ4">yield</span><span class="p" id="mwAZ8">)</span> <span class="w" id="mwAaA"> </span><span class="p" id="mwAaE">(</span><span class="nb" id="mwAaI">call-with-current-continuation</span> <span class="w" id="mwAaM"> </span><span class="c1" id="mwAaQ">;; Capture the continuation representing THIS call to yield</span> <span class="w" id="mwAaU"> </span><span class="p" id="mwAaY">(</span><span class="k" id="mwAac">lambda</span><span class="w" id="mwAag"> </span><span class="p" id="mwAak">(</span><span class="nf" id="mwAao">cont</span><span class="p" id="mwAas">)</span> <span class="w" id="mwAaw"> </span><span class="c1" id="mwAa0">;; Stick it on the ready list</span> <span class="w" id="mwAa4"> </span><span class="p" id="mwAa8">(</span><span class="k" id="mwAbA">set!</span><span class="w" id="mwAbE"> </span><span class="nv" id="mwAbI">ready-list</span> <span class="w" id="mwAbM"> </span><span class="p" id="mwAbQ">(</span><span class="nb" id="mwAbU">append</span><span class="w" id="mwAbY"> </span><span class="nv" id="mwAbc">ready-list</span> <span class="w" id="mwAbg"> </span><span class="p" id="mwAbk">(</span><span class="nb" id="mwAbo">list</span><span class="w" id="mwAbs"> </span><span class="nv" id="mwAbw">cont</span><span class="p" id="mwAb0">)))</span> <span class="w" id="mwAb4"> </span><span class="c1" id="mwAb8">;; Get the next thread, and start it running.</span> <span class="w" id="mwAcA"> </span><span class="p" id="mwAcE">(</span><span class="k" id="mwAcI">let</span><span class="w" id="mwAcM"> </span><span class="p" id="mwAcQ">((</span><span class="nf" id="mwAcU">cont</span><span class="w" id="mwAcY"> </span><span class="p" id="mwAcc">(</span><span class="nb" id="mwAcg">car</span><span class="w" id="mwAck"> </span><span class="nv" id="mwAco">ready-list</span><span class="p" id="mwAcs">)))</span> <span class="w" id="mwAcw"> </span><span class="p" id="mwAc0">(</span><span class="k" id="mwAc4">set!</span><span class="w" id="mwAc8"> </span><span class="nv" id="mwAdA">ready-list</span><span class="w" id="mwAdE"> </span><span class="p" id="mwAdI">(</span><span class="nb" id="mwAdM">cdr</span><span class="w" id="mwAdQ"> </span><span class="nv" id="mwAdU">ready-list</span><span class="p" id="mwAdY">))</span> <span class="w" id="mwAdc"> </span><span class="c1" id="mwAdg">;; Run it.</span> <span class="w" id="mwAdk"> </span><span class="p" id="mwAdo">(</span><span class="nf" id="mwAds">cont</span><span class="w" id="mwAdw"> </span><span class="no" id="mwAd0">#f</span><span class="p" id="mwAd4">)))))</span>

In 1999, David Madore (inventor of the Unlambda programming language) accidentally discovered a 12-character Unlambda term, making use of call/cc, that printed all natural numbers sequentially in unary representation: ``r`ci`.*`ci. This program and the apparent mystery surrounding its effect have attracted some attention, and are commonly known as the yin-yang puzzle. A Scheme translation, provided by Madore, is as follows:

<span id="mwAe0"></span><span class="p" id="mwAe4">(</span><span class="k" id="mwAe8">let*</span><span class="w" id="mwAfA"> </span><span class="p" id="mwAfE">((</span><span class="nf" id="mwAfI">yin</span> <span class="w" id="mwAfM"> </span><span class="p" id="mwAfQ">((</span><span class="k" id="mwAfU">lambda</span><span class="w" id="mwAfY"> </span><span class="p" id="mwAfc">(</span><span class="nf" id="mwAfg">cc</span><span class="p" id="mwAfk">)</span><span class="w" id="mwAfo"> </span><span class="p" id="mwAfs">(</span><span class="nb" id="mwAfw">display</span><span class="w" id="mwAf0"> </span><span class="o" id="mwAf4">#</span><span class="err" id="mwAf8">\</span><span class="k" id="mwAgA">@</span><span class="p" id="mwAgE">)</span><span class="w" id="mwAgI"> </span><span class="nv" id="mwAgM">cc</span><span class="p" id="mwAgQ">)</span><span class="w" id="mwAgU"> </span><span class="p" id="mwAgY">(</span><span class="nb" id="mwAgc">call-with-current-continuation</span><span class="w" id="mwAgg"> </span><span class="p" id="mwAgk">(</span><span class="k" id="mwAgo">lambda</span><span class="w" id="mwAgs"> </span><span class="p" id="mwAgw">(</span><span class="nf" id="mwAg0">c</span><span class="p" id="mwAg4">)</span><span class="w" id="mwAg8"> </span><span class="nv" id="mwAhA">c</span><span class="p" id="mwAhE">))))</span> <span class="w" id="mwAhI"> </span><span class="p" id="mwAhM">(</span><span class="nf" id="mwAhQ">yang</span> <span class="w" id="mwAhU"> </span><span class="p" id="mwAhY">((</span><span class="k" id="mwAhc">lambda</span><span class="w" id="mwAhg"> </span><span class="p" id="mwAhk">(</span><span class="nf" id="mwAho">cc</span><span class="p" id="mwAhs">)</span><span class="w" id="mwAhw"> </span><span class="p" id="mwAh0">(</span><span class="nb" id="mwAh4">display</span><span class="w" id="mwAh8"> </span><span class="o" id="mwAiA">#</span><span class="err" id="mwAiE">\</span><span class="nb" id="mwAiI">*</span><span class="p" id="mwAiM">)</span><span class="w" id="mwAiQ"> </span><span class="nv" id="mwAiU">cc</span><span class="p" id="mwAiY">)</span><span class="w" id="mwAic"> </span><span class="p" id="mwAig">(</span><span class="nb" id="mwAik">call-with-current-continuation</span><span class="w" id="mwAio"> </span><span class="p" id="mwAis">(</span><span class="k" id="mwAiw">lambda</span><span class="w" id="mwAi0"> </span><span class="p" id="mwAi4">(</span><span class="nf" id="mwAi8">c</span><span class="p" id="mwAjA">)</span><span class="w" id="mwAjE"> </span><span class="nv" id="mwAjI">c</span><span class="p" id="mwAjM">)))))</span> <span class="w" id="mwAjQ"> </span><span class="p" id="mwAjU">(</span><span class="nf" id="mwAjY">yin</span><span class="w" id="mwAjc"> </span><span class="nv" id="mwAjg">yang</span><span class="p" id="mwAjk">))</span>

Sign in to keep reading

3 more chapters are free with a Lably account.

More in Computing and communication