(The yield new syntax could be made a bit cleaner with static
What do you mean here? I am not entirely familiar with coroutines and don't
think I've run across this concept.
(btw, ooops--Forgot the updated iterator syntax in my examples...not that it
matters much).
Oh, I was just saying that rather than doing a bunch of "yield return new
MoveToCommand(pos);" it could be made to read better with a static method
that returns a command, such as "yield return MoveTo(pos);", and if the
yield could be fired in another method, then you could just simply to
"MoveTo(pos);"--saving two keywords and improving readability a bit
(although you will get no indication that execution will yield--so maybe
it's not such a good idea).
But the yield statement (as I understand) has to be in the iterator method,
in order to make the job of the compiler MUCH easier--since these do seem to
be compiled IEnumerable classes and not true fibers or coroutines.
This also means that, given a binary tree class, one can not use a recursive
method to traverse the tree. The following
won't be legal:
public IEnumerable InOrder {
get {
if(root != null) Iterate(root);
yield break;
}
}
public void Iterate(Node node) {
if(node.Left != null) Iterate(left);
yield return node.Value;
if(node.Right != null) Iterate(right);
}
If this were legal, then iterating even the most complicated of data
structures would be easy. However, since the "yield" cannot occur outside
of your iterator method, it won't be as simple as that.
Mind you, it will be easier to write with iterators in this case than it was
with a separate Enumerator class, but not too much easier...You still need
to use your own Stack to do the iteration efficiently. The only difference
is the Stack is now a local variable and not a instance variable.
I can't really think of a way to easily allow the iterator to be recursive
without being build on top of fibers or coroutines. Maybe I'm missing
something here. Maybe the compiler could determine which routines
containing yield statemetns were called by an iterator and somehow figure
out how to build the pseudo-call-stack for you. It would also mean that any
routine containing a yield could not be called by a non-iterator method. In
the end, it seems like it'd be more complicated than it is worth (especially
when you take security into account).
Using a quick scan across the mono code for iterators, it certaily appears
to create a nested class that handles iteration, so a sort of
psuedo-coroutines.
I had forgot that Mono already has these features in it (at least
partially).
I guess I should play around with my Mono install some more and see if my
idea for using iterators to simulate true coroutines in a simulation will
work well.
A pure coroutine api would be nice, a fiber API is probably best left
out(There is no guarentee that a managed Thread is actually a thread, it
could be implemented as a fiber or a user thread(similar to fibers) in a
given runtime.
I forgot about that, too, but it makes sense, with Unix being fiber-based
and not thread-based
I'm not sure, realistically, what IL can do as far as
coroutines go, but the Mono generation uses standard return and the
contained class for state. This method should work(its how anonymous methods
work as well, the only thing I am really worried about is how would security
work in full coroutines, if the stack isn't maintained.
Yeap, I forgot this too. I tend to forget about code security when I think
about features that IL should have implemented. Still, aren't there some
languages that can't be fully supported in IL because of it's lack of
coroutines? Or would all of those compilers have to do pseudo-coroutines as
well? Then what about the previously mentioned problem of calling other
methods from the coroutine?
Or do .NET versions of these languages have to be restricted in what they
support? Or maybe the compiler could make use of an unmanaged fiber API.
But this brings up security issues again.
I guess this is similar to the problem of getting SmallTalk to run on IL. I
guess some language features have to be left out.
The prolog for a routine would have to maintain security, complicating
the language a bit(It wouldn't be particularly safe, IMHO, to change the
security of a method already executing in part between calls, thats asking
for obscure bugs).
Right you are. I need to think about the problem some more.
But for now I'm happy with the (proposed) implementation of iterators. It
certainly makes it easier to provide multiple enumerators for a data type
(such as reverse enumerators, enumerators that only travel through a subset
of the items, etc.).
Back to my example:
After thinking about it, perhaps a better way to implement the problem in my
example is as follows: (The bot-manager class would be in charge of
checking the Action property after each MoveNext call.)
class Bot : Entity {
private IAction action;
private int health;
// etc.
public IAction Action {
get { return action; }
}
private void Attack(Entity entity) {
action = new AttackAction(entity);
}
private void MoveTo(Point location) {
action = new MoveToAction(location);
}
private void WanderAimlessly() {
action = new WanderAimlesslyAction();
}
// bool return value is a dummy, I suppose (I need to think about
whether a value is really needed)
// I don't suppose IEnumerable<Void> is possible .. ???
public IEnumerable<bool> Update {
get {
while(alive) {
if(health < 10) {
Entity healthPickup =
World.FindNearestHealthPickup(this.Location);
MoveTo(healthPickup.Location);
yield return true;
}
else {
Entity[] enemies =
World.GetVisibleEnemies(this.Location);
if(enemies.Length > 0) {
Attack(enemies[0]);
yield return true; // odd syntax, but it does the
job I guess
}
else {
WanderAimlessly();
yield return true;
}
}
}
yield break;
}
}
}
This is still pretty clean, and abstracts away a lot of the state
management. It's a good example one of my favorite situations in
programming---lots of messy ground work in order to make the higher-level
code cleaner. I still wish the "yield return true" statement could be
better....like I said, if non-iterator private-methods could yield, then
that statement could go in MoveTo(), Attack(), and WanderAimlessly();
Maybe I just miss the pre-multitasking days of programming in DOS where you
never had to worry about programming finite-state-machines all the time.
*sigh*. These "problems" are even worse in games, and although threads can
help, they often open a whole new can of worms.
Now time to go play with Mono a bit... Maybe I can improve my
implementation further. I'm curious to what the generated IEnumerable
class's source will look like if I put really complicated logic into the
iterator method.
--Matthew W. Jackson