I used to code a lot in a language called Supercollider, which is a domain-specific language for sound synthesis and computer music.
One really nice feature it has is coroutines. Python also has these (via the yield keyword) but Supercollider's ones are much more powerful*, and I've often wondered why none of the other languages I'm familiar with have a similar feature.
*or at least I thought they were when asking this question, now I'm not so sure. yield from didn't exist when I learned Python and I wasn't aware of it when writing the question.
I'm using the terminology from SuperCollider here, which might be idiosyncratic, as it seems that "routines" are more commonly called "generators". The kind of coroutines I'm talking about are only about control flow, not concurrency - they run until they hit a yield and then return control to their caller until the caller chooses to resume them. (In SuperCollider you can also schedule them on a clock, but that's kind of a separate mechanism and is optional.)
In the rest of this question I will describe SuperCollider's coroutines, in order to make it clear what I mean. Like Python, SuperCollider is a dynamically typed interpreted language.
As in Python, SuperCollider has a yield keyword. (Technically a method in SuperCollider.) So you can write a function like this
~myFunction = {
var x = 10;
while {x>0} {
yield(x);
x = x - 1;
};
yield("I've run out of x's!");
};
// note on SuperCollider's syntax: the { ... } syntax creates a Function
// object, which we assign to the global variable ~myFunction.
You can then use this to create an object (called a routine in Supercollider, or a generator in Python) that will give a different output each time it's called, in this case counting down from 10 to 1 and then outputting "I've run out of x's!". In SuperCollider that looks like this:
// here we create a Function object and then pass it to the Routine constructor
~myRoutine = Routine({
var x = 10;
while {x>0} {
yield(x);
x = x - 1;
};
yield("I've run out of x's!");
});
postln(~myRoutine.next); // 10
postln(~myRoutine.next); // 9
// ...
So far this is just the same as Python's generators. However, in Python the yield keyword always yields from the function it's in, so you can't have a coroutine that calls another function and then yields from that function. In SuperCollider you can do this, because the yield command yields from the enclosing Routine. This allows things like the following example, which behaves identically to the previous one:
~countDownFrom = {
arg x;
while {x>0} {
yield(x);
x = x - 1;
};
};
~myRoutine = Routine({
~countDownFrom.(10); // call the function ~countDownFrom with argument 10
yield("I've run out of x's!");
});
postln(~myRoutine.next); // 10
postln(~myRoutine.next); // 9
// ...
There are also other useful things you can do with routines in SuperCollider, such as embedding one inside another or composing them.
I always found this sort of thing very useful in SuperCollider. (It's particularly useful in music, where routines can be used to produce sequences of notes, but I can imagine a lot of uses in things like game programming as well.) My question is just why it seems to be so rare in other languages - Python seems unusual in having a yield keyword at all, and even then it's very restricted compared to SuperCollider's one.
Is it because the language really has to be interpreted in order for this to work? Would there be impassible barriers that would prevent it from being implemented in a strongly typed, compiled language?
LazyList.unfoldto provide similar behaviour toyield(it's not a structure, but it could theoretically be turned into a structure) $\endgroup$yield fromconstruct to yield every item from some other generator. It appears to work similarly to the~construct in SuperCollider $\endgroup$yield()without wrapping it in aRoutine()? $\endgroup$