State Machines in Go
denis.papathanasiou.org
denis.papathanasiou.org
Related: Rob Pikes' lexical scanning talk (state machines in Go): http://blog.golang.org/2011/09/two-go-talks-lexical-scanning...
type State int
type Transition int
func (s State) Next(t Transition) (State, error) {
/* doubly nested switch goes here */
} type state struct {
// add any fields you need to persist here
}
type transition func(*state) transition
same as in Lexical Scanning in Go (http://www.youtube.com/watch?v=HxaD_trXwRE)If you wanted to extend it to perform actions on state change, I'd extend the return values to (State, Action, error) where Action is "type Action int" as well, and use the Action in another switch. Thus you are free to pick which actions you care about, and test the actions separately from the state engine.
(1) Pike's is more static: there is no Machine struct and states are identified with handlers so that there is no table lookup in the loop.
(2) Pike chose to go with a modifiable lexer struct rather than threading a cargo value through the machine.
(3) Pike used nil for all end states.
The main design questions are probably:
(a) Do you need to construct machines at runtime?
(b) Do you need state names available at runtime?
http://eli.thegreenplace.net/2009/08/29/co-routines-as-an-al...
Basically use stack state and goroutines to avoid explicit states. Unlike Python, Go might take multiple threads to do this?
For an simple example--written in many languages including Python and Go--with two stateful coroutines, you can check this out: http://rosettacode.org/wiki/Synchronous_concurrency
That is, Go doesn't provide synchronous dataflow. Thanks for the link, I will be looking at that!
edit: or maybe it does with a channel size of 1? The Rob Pike lexer talk mentioned a few times here was doing some trick with that.
And you're on the right track regarding how channels work. They can be synchronous if you like. Effectively, such channels have no buffering. Also, you don't need to use OS threading at all to solve such problems in Go. It's possible to run code with lots of cooperating goroutines in a single OS thread.
Edit: By the way, I'm not familiar with Python so I'm just guessing at how the send method and the yield form work.
Edit 3: Notice that I forgot to close the "frames" channel. So if this were a long-running program, that goroutine running frameReceiver would be a leaked resource.
One of the most well-known uses of Ragel is for Zed Shaw's mongrel HTTP parser. If you want an event-driven server, and you want to parse incrementally, you need a state machine. And writing a state machine for HTTP is a nightmare -- for C it is indeed better to generate it.
But I took a look at Go's http/request.go, and it just parses in the normal "synchronous" style. Of course this is because the Go runtime already takes care of the event loop and dispatching to goroutines.
I just did the same thing in Python -- I wrote a coroutine-based HTTP "framer" in a single 20-line function -- it uses ReadUntil('\r\n\r\n'), parses Content-Length, and ReadN(length). This is all done concurrently on a single thread of course, thanks to the coroutines.
This seems like obviously the right way to parse network formats efficiently and scalably. So yeah I would think twice about before using state machines in Go.