View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide

A few notes on Routines

Home   How To   Code Pool   Public Library   Theory   Events
from what i can ascertain Routines in SC are technically called, 'first class, stackful, asymmetric, explicitly sequenced coroutines', i.e. they can be passed around like any other object (first class), they have their own stack which means that you can yield from functions and methods deeper than the top level function you originally initialized the routine with (stackful), there are two functions (yield/next) used for passing control back and forth between routines, not just one (asymmetrical as opposed to symmetrical), and control flow between coroutines is cooperative and user defined (explicitly sequenced). symmetric coroutines can be implemented in terms of asymmetric ones (and vice-versa), hence SC also supports symmetric coroutines (see example 4 below).

there are some issues with the nomenclature regarding coroutines in the literature that i think people should be aware of.

on occasion you see asymmetric coroutines called 'semi-coroutines' (like in Simula), but the term is best avoided because it is also used to describe stackless coroutines like Python's 'generators'. the term 'generator' is also sometimes used to mean asymmetric coroutines (as implemented in the language IPL-V). to further confound the matter in his book 'A Little Smalltalk', Timothy Budd defines an abstract mechanism which responds to the message 'next' as a Generator or AbstractGenerator, which in SC we call a stream. you might also see implicitly sequenced routines, or what are essentially preemptive threads called 'coroutines', but it is much more common for the term to refer to explicit coroutines like SC's Routines.

suffice it to say that the name clashing described above is due to the close similarity between the various mechanisms/concepts being juggled and the many accidents of history. i'll try to clarify from an SC-centric perspective:

in SC, stream semantics are implemented for all objects in the form of \next and \reset messages, as well as a host of others (see Object). all objects who do not redefine these methods simply return themselves on calls to both \next and \reset. in SC the class Object implements (more or less) T. Budd's AbstractGenerator interface, or, in other words, because all objects subclass Object, everything is a stream and hence a T. Budd Generator.
10.do { 4.next.postln }

not everything is a coroutine though, but because Routines are first class in SC and because they can behave as streams then they also qualify as T. Budd Generators. so we have the potentially very confusing situation that Routines (coroutines) are sometimes called generators, Routines implement stream semantics, and streams are sometimes called generators :) i would recommend sticking to the nomenclature and taxonomy that i outlined in the first paragraph especially when talking to non-sc'ers about SC Routines.

some pretty things you can do with coroutines:

1) using a routine to iterate over a recursive data structure, in this case a binary tree:
(
var genTree, tree, treeDepth=4, drill, treeIter, n=0, val;

genTree = { |maxDepth|
	var subTreeOrNil;
	subTreeOrNil = [{genTree.(maxDepth-1)}, nil];
	if( maxDepth == 0 ) {
		nil
	} {
		( 
			v: (n = n + 1), 
			l: subTreeOrNil.choose.value, 
			r: subTreeOrNil.choose.value 
		)
	}
};
tree = genTree.(treeDepth).postln;

drill = { |node|
	node !? {
		drill.(node[\l]); 
		node[\v].yield;
		drill.(node[\r]);
	}
};
treeIter = r { drill.(tree) };

while { (val = treeIter.next).notNil } {
	val.postln
}
)

2) note that in the above example the data structure being operated on is recursively defined and so it is quite natural for the traversal algorithm also to be so, but there are cases where recursive procedures are clean solutions to problems involving flat structures like arrays. this example uses a routine to iterate over all permutations on an array 'in place', i.e. the array is never copied:
(
var arr, recPurm, iter, makeIter, swap;
arr = (0..3);
recPurm = { |a, sz|
	var szm1;
	szm1 = sz - 1;
	if( sz == 0 ) {
		a.yield;
	} {
		sz.do { |i|
			a.swap(szm1, i);
			recPurm.(a, szm1);
			a.swap(i, szm1);
		}
	}
};
makeIter = { |a| r { recPurm.(a, a.size) } };
iter = makeIter.(arr);

24.do { iter.next.postln }
)

3) a coroutine as an escape procedure. in situations where you have deeply nested function calls this technique might come in handy. it dramatically reduces the number of operations performed by stopping the computation in it's tracks if need be, using the coroutine only once, and then discarding it. what's really happening is that the coroutine's stack is never allowed to fully unwind and is garbage collected as is.
(
var tree1, tree2, treeProduct, mul;

tree1 = ( v: 4, l: ( v: 5,  l: (v: 2), r: (v: 0)), r: (v: 2));
tree2 = ( v: 4, l: ( v: 5,  l: (v: 2), r: (v: 9)), r: (v: 2));
mul = { |x,y| Post << $*; x*y }; // just a more verbose * operator

// get the product of all the tree node's \v fields
treeProduct = { |tree|
	var recProduct;
	recProduct = { |tree|
		var val;
		if( tree.isNil ) { 1 } {
			val = tree[\v];
			if( val == 0 ) { 
				// if there are no zeroes, then this yield is never called and
                                // only the yield defined further below has a chance to run
				0.yield
			} {
				// val * (recProduct.(tree[\l]) * recProduct.(tree[\r]));
				mul.(val, mul.(recProduct.(tree[\l]), recProduct.(tree[\r])))
			}
		}
	};
	Routine({
		recProduct.(tree).yield;
	}).value;
};

treeProduct.(tree1).postln;
treeProduct.(tree2)
)

4) symetric coroutines, this one is a real mind bender
(
var mainThread, currThread, transfer, r1, r2, stringCollect;
mainThread = currThread = thisThread;
transfer = { |inThread, val|
	var break=false, ret;
	if( currThread === mainThread ) {
		while { break.not } {
			currThread = inThread;
			if( inThread === mainThread ) {
				break = true;
				ret = val;
			} {
				ret = inThread.next(val);
				if( ret.isNil ) {
					break = true;
					currThread = mainThread;
				} {
					#inThread, val = ret;
				}
			}
		};
		ret
	} {
		[inThread, val].yield;
	}
};
stringCollect = r { |word| 
	[r2,r1,r2,r1].do { |r| 
		word = word + transfer.(r);
	};
	transfer.(mainThread, word);
};
r1 = r {
	["this","so","fun"].do { |word| transfer.(stringCollect,word) }
};
r2 = r {
	["is","much"].do { |word| transfer.(stringCollect,word) }
};
Post << "stringCollect: " << transfer.(r1) << $! << nl;
)


my sources for this document in case you want to follow them up:

BUDD, T., 1987, A Little Smalltalk, Addison Wesley
MARLIN, C., 1980, Coroutines, Springer-Verlag (Lecture Notes in Computer Science #95)
MOURA, A., RODRIGUEZ, N., IERUSALIMSCHY, R., Coroutines in Lua*
MOURA, A., IERUSALIMSCHY, R., 2004, Revisiting Coroutines*

*papers available online, just google them

ccos

Links to this Page