Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

implementing a state machine using the "yield" keyword

Is it feasible to use the yield keyword to implement a simple state machine as shown here. To me it looks like the C# compiler has done the hard work for you as it internally implements a state machine to make the yield statement work.

Can you piggy-back on top of the work the compiler is already doing and get it to implement most of the state machine for you?

Has anyone done this, is it technically possible?

like image 504
Matt Warren Avatar asked Jul 28 '09 15:07

Matt Warren


People also ask

What is the yield keyword used for?

The yield keyword is use to do custom stateful iteration over a collection. The yield keyword tells the compiler that the method in which it appears is an iterator block. yield return <expression>; yield break; The yield return statement returns one element at a time.

How yield is implemented?

yield works by building a state machine internally. It stores the current state of the routine when it exits and resumes from that state next time. You can use Reflector to see how it's implemented by the compiler.

What is the use of yield keyword in C#?

You use a yield return statement to return each element one at a time. The sequence returned from an iterator method can be consumed by using a foreach statement or LINQ query. Each iteration of the foreach loop calls the iterator method.


4 Answers

It's feasible but it is a bad idea. Iterator blocks were created to help you write custom iterators for collections, not for solving the general-purpose problem of implementing state machines.

If you want to write a state machine, just write a state machine. It's not hard. If you want to write a lot of state machines, write a library of useful helper methods that let you cleanly represent state machines, and then use your library. But don't abuse a language construct intended for something completely different that just happens to use state machines as an implementation detail. That makes your state machine code hard to read, understand, debug, maintain and extend.

(And incidentally, I did a double-take when reading your name. One of the designers of C# is also named Matt Warren!)

like image 138
Eric Lippert Avatar answered Oct 05 '22 08:10

Eric Lippert


Yes, it's absolutely possible and easy to do. You can enjoy using control flow constructs (for, foreach, while, ... goto (using goto particularly suits this scenario ;))) along with yields to build one.

IEnumerator<State> StateMachine
             (Func<int> currentInput /* gets current input from IO port */, 
              Func<int> currentOutput) {
    for (;;)  {
       if ((currentInput() & 1) == 0) 
           yield return new State("Ready"); 
       else {
           if (...) {
               yield return new State("Expecting more data");
               SendOutput(currentOutput());
               while ((currentInput() & 2) != 0) // while device busy
                    yield return new State("Busy");
           else if (...) { ... } 
       }
    }
}

// consumer:
int data;
var fsm = StateMachine(ReadFromIOPort, () => data);
// ...
while (fsm.Current != "Expecting more data")
    fsm.MoveNext();
data = 100;
fsm.MoveNext();
like image 24
mmx Avatar answered Oct 05 '22 07:10

mmx


Iterator blocks do indeed implement state machines, but the tricky bit is getting the next input. How are you going to know where to move next? I guess you could have some sort of shared "current transition" variable, but that's somewhat icky.

If you don't need any input (e.g. your state machine is just cycling between states) then it's easy, but that's not the interesting kind :)

Can you describe the kind of state machine you're interested in?

like image 32
Jon Skeet Avatar answered Oct 05 '22 08:10

Jon Skeet


While this is not a state machine in the classical sense, the article about Iterator-based Micro Threading uses yield creatively for state-based actions.

IEnumerable Patrol ()
{
    while (alive){
        if (CanSeeTarget ()) {
            yield return Attack ();
        } else if (InReloadStation){
            Signal signal = AnimateReload ();
            yield return signal;
        } else {
            MoveTowardsNextWayPoint ();
            yield return TimeSpan.FromSeconds (1);
        };
    }
    yield break;
}
like image 44
Michael Stum Avatar answered Oct 05 '22 09:10

Michael Stum