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
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.)
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
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
}
end
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)
@C2
end
procedure producer(args...)
repeat {
val := ...
ret := val @ &source
}
end
procedure consumer(args..., c)
value := @c
repeat {
# process value
...
value := retval @ &source
}
end
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.
© 1997 Shamim Mohamed