I was inspired by Doug McIlroy’s paper, “Squinting at Power Series”, a captivating introduction to the communicating sequential processes (CSP) model of concurrent programming: some processes generate coefficients of a power series, while other processes read coefficients on input channels and write their output on another channel as soon as possible. Each output coefficient is written as soon as it is known, before reading more input. Like Unix pipes, these processes are connected together to compute sums, products, and so on.

For example, a process that repeatedly spits out 1 on its output channel represents the power series 1 + x + x2 + … This is similar to running

$ yes 1

This process could be fed into another process that produces the derivative of its input. A Unix equivalent might be:

$ yes 1 | awk \'{ print (NR-1)*$0 }\'

Continued fractions likewise naturally fit the CSP model, as Gosper suggets. The simplest are processes that output terms according to some pattern while maintaining barely any internal state. Other processes can then consume these terms to compute convergents, decimal expansions, sums, products, square roots and so on.

Thus we spawn at least one thread per continued fraction or operation. These threads communicate via crude demand channels, sending terms of continued fractions back and forth.

For example, consider the code for generating e = 2.71828…

// e = [2; 1, 2, 1, 1, 4, 1, ...]
static void *e_expansion(cf_t cf) {
  int even = 2;
  cf_put_int(cf, even);

  while(cf_wait(cf)) {
    cf_put_int(cf, 1);
    cf_put_int(cf, even);
    even += 2;
    cf_put_int(cf, 1);
  }

  return NULL;
}

The function cf_put places a term on the output channel, and cf_wait is our implementation of demand channels, a sort of cooperative multitasking. Without it, not only would we have to destroy and clean up after the thread ourselves, but more seriously, the thread might consume vast amounts of resources computing unwanted terms. The cf_wait functions instructs the thread to stay idle. Our threads call this function often, and if it returns zero, our threads clean themselves up and exit.

Threads are the future, if not already the present. Multicore systems are already commonplace, and as time passes, the number of cores per system will steadily march upward. Happily, this suits continued fractions.