Co-expressions in Icon

Shamim Mohamed

A co-expression can be thought of as an independent, encapsulated thread-like context, where the results of the expression can be picked off one at a time. Let us consider an example: suppose you are writing a program that generates code, and you need something that will generate names for you. This expression will generate names:

   "name" || seq()
(seq produces an infinite sequence of integers, by default starting at 1.) Of course, an expression exists at one point in the code; we need to separate the evaluation of the expression from its textual position in the program. We do this by creating a co-expression:
   c := create ("name" || seq())
Now wherever a label name is desired, it can be obtained by activating the co-expression c:
   tempvar_name := @c

After a co-expression has produced all its results, further evaluation with @ will fail. The ^ operator produces a new co-expression with the same expression as its argument, but `rewound' to the beginning.

   c := ^c

Parallel evaluation

Coexpressions can be used to evaluate a number of expressions `in parallel' or in lock-step. We want to create a table of the ASCII characters with the hex, decimal and octal equivalents:

   dec := create(0 to 255)
   hex_dig := "0123456789abcdef"
   hex := create(!hex_dig || !hex_dig)
   oct := create((0 to 3) || (0 to 7) || (0 to 7))
   char := create image(!&cset)
   while write(@dec, "\t", @oct, "\t", @hex, "\t", @char)
Every invocation of write results in all the coexpressions being activated once, so they are all run in lock-step.

Parallel evaluation can also be used to assign to a set of variables:

   s := @ create !stat(f)
   every (dev | ino | mode | link |  uid | gid) := @ s
(The stat function returns information about a file.)

User-defined control structures

Since an expression (not just the value that it evaluates to) can be an argument to a procedure, coexpressions can be used to implement new control structures. Consider a control structure that selects values from the first expression at the positions specified by the second. This could be invoked as:

   seqsel([create fibonacci(), create primes()])
Assuming that we have already written generators that produce the fibonacci numbers and the primes, this expression should produce the numbers tex2html_wrap_inline36. Here is the implementation of seqsel:
procedure seqsel(a)
   (*a = 2) | stop(...)

   e1 := a[1]; e2 := a[2]
   index := 1

   repeat {
      (i := @e2 ) | fail
      every index to i do
         (value := @e1) | fail
      suspend value
      index := i+1

Icon provides a syntactic short-cut for this kind of usage:

   proc([create e1, create e2, ..., create en])
can also be written as
   proc{e1, e2, ..., en}


In a convential procedure invocation scenario, the procedures have an asymmetric relationship; every time control is transferred from the calling procedure to the callee, the slave procedure starts execution at the top. Coroutines have an equal relationship: when control is transferred from one coroutine to another, it starts executing from the previous point that its execution was suspended from. This process is called resumption. The `producer/consumer problem' is a good example of procedures that have an equal relationship.


Here's an example:

procedure main()
   C1 := create consumer(args...)
   C2 := create producer(args..., C1)

procedure producer(args...)
   repeat {
      val := ...
      ret := val @ &source

procedure consumer(args..., c)
   value := @c
   repeat {
      # process value
      value := retval @ &source
When producer resumes consumer, it passes it a value of value; the consumer gets this value, and passes a return code (retval) back. &source is the coexpression that activated the current coexpression.

Note: This should not be taken to mean that the prducer/consumer problem should be done with coroutines! It is hard to find short examples that require coroutines for a clear solution.

About this document ...

© 1997 Shamim Mohamed