Go's idiom for state machines

3,216 views
Skip to first unread message

nsf

unread,
Jun 20, 2010, 9:43:57 PM6/20/10
to golan...@googlegroups.com
Lately, I was trying to come up with some kind of Go idiom for state
machines. And I've found an interesting solution. However, I'm not sure
is it legal and/or a good solution for that problem. Here is the code:

---------------------------------------------------------------------
package main

import (
"fmt"
)

// our state machine interface, it may consist of one or many methods
// which behaviour depends on a particular state
type StateInterface interface {
Test()
}

// the state itself may contain some data, it is an interface also O_o
// NOTE: there is no need to specify any *State methods
type State struct {
StateInterface
name string
counter int
}

// let's define a bunch of different states
type StateA State
func (self *StateA) Test() {
fmt.Println("state A")
self.counter++
if self.counter > 1 {
// change state
self.StateInterface = (*StateB)(self)
}
}

type StateB State
func (self *StateB) Test() {
fmt.Println("state B")
}

type StateC State
func (self *StateC) Test() {
fmt.Printf("state C, hello %s\n", self.name)

// change state
self.StateInterface = (*StateA)(self)
}

func main() {
// initial setup
s := &State{nil, "nsf", 0}
s.StateInterface = (*StateC)(s)

for i := 0; i < 5; i++ {
// we can call directly StateInterface methods on *State
s.Test()
}
}
---------------------------------------------------------------------
OUTPUT:

[nsf @ ~]$ ./8.out
state C, hello nsf
state A
state A
state B
state B
---------------------------------------------------------------------

Well it's sort of a thing that some people wanted in C++ for a long
time. An ability to change the vtable directly. For example that way we
can change the whole behaviour or its part of a certain "class". And it
looks like a code that supports it is very straightforward.

But I'm totally unaware what's happening underneath, probably there are
some extra pointers involved in case if we have more than one interface
(aka customizable behaviour) in such a "class".

Anyway, what do you think?

Kai Backman

unread,
Jun 21, 2010, 3:47:12 AM6/21/10
to nsf, golan...@googlegroups.com
On Mon, Jun 21, 2010 at 4:43 AM, nsf <no.smi...@gmail.com> wrote:
> Anyway, what do you think?

I find myself doing state machines using the program counter of a
goroutine to hold the actual state combined with labels and goto.

func main() {
go stateMachine()
// other stuff
}

func stateMachine() {
// init
s0:
if foo {
goto s2
}
goto s3

s2:
// more s2 stuff

s3:
// s3 stuff
}

roger peppe

unread,
Jun 21, 2010, 9:49:38 AM6/21/10
to nsf, golan...@googlegroups.com
yes, the fact that a concrete type can inherit methods
from an interface type (or several) is cool.

an idiom i've been
finding useful is where i want to create an object A
that satisfies a particular interface X and i already have
another object B that provides a superset
of that interface. A can inherit all of B's X methods
without making any more public, while retaining
the capacity to use B's extra methods. i do this by having
two instance variables both pointing to B; one
is the desired interface type (unnamed) and one
the concrete instance of B.

as an example, in my experimental graphics package,
there's an interface that lets an object draw itself:

// slightly simplified version
type Item interface {
Draw(dst draw.Image, clip draw.Rectangle)
Bbox() draw.Rectangle
}

there's a low-level object that implements Item, and can display an image,
but allows direct access to its fields and lacks for
some convenience methods.

type ImageItem struct {
Image draw.Image
Opaque bool
R draw.Rectangle
}

to create a higher level object, layered onto ImageItem,
i can do:

type MyItem struct {
Item
img ImageItem
// ... and other fields
}

func NewMyItem() *MyItem {
i := new(MyItem)
i.img.Image = image.NewRGBA(...)
i.img.Opaque = true
i.R = draw.Rect(...)
i.Item = &i.img
return i
}

in this way, i can choose exactly which
methods of any object to inherit.

it seems to work well, and it doesn't violate
any encapsulation properties - i could always
get the same effect simply by adding pass-through
methods to MyItem for all methods in Item.

David Leimbach

unread,
Jun 21, 2010, 1:43:46 PM6/21/10
to golang-nuts
For a state machine I think you can leverage closures and message
passing to get something fairly expressive.

As a quickly whipped up example see below:


===================
package main

import (
"fmt"
"os"
"bufio"
)

const combination string = "1234"


// A type of function that has it's own sort of type as its return
value...
type nextstate func (chan byte, chan bool, string) (string, nextstate)


// locked is a state, with its own logic for state transition inside
it.
func locked(ch chan byte, isLocked chan bool, input string) (string,
nextstate) {
c := <- ch
curstring := input + string([]byte{c})
if len(curstring) == len(combination) {
if curstring == combination {
return unlocked(ch, isLocked, "");
} else {
isLocked <- true
fmt.Fprintf(os.Stderr, "Wrong combination! Start over\n")
return "", locked
}
}
isLocked <- true
return curstring, locked
}


// unlocked is a state that prints a message, tells of the unlocked
status, and then goes back to locked.
func unlocked(ch chan byte, isLocked chan bool, input string)
(string, nextstate) {
fmt.Fprintf(os.Stderr, "Correct combination! Re-locking!\n")

isLocked <- false
return "", locked
}


// A goroutine function that runs through state transitions without
knowing anything but the initial state.
// this allows us to keep our state transitions, and the number of
states somewhat separated logically
// from the act of running the states.
func runstate(ch chan byte, isLocked chan bool) {
newstate := locked
curstring := ""
for ;; {
curstring, newstate = newstate(ch, isLocked, curstring)
}
}

// main just grabs some IO, starts up the goroutine for the state
transitions, and talks to the goroutine about the input.
func main () {

ch := make(chan byte)
isLocked := make(chan bool)

go runstate(ch, isLocked)

in := bufio.NewReader(os.Stdin)
var c byte
var err os.Error
var stillLocked bool

// note ReadByte would be better replaced by something like a curses-
like getch for this demo or some sort of single char reader.
for {
c, err = in.ReadByte()
if err != nil {
panic("Reading stderr")
}
ch <- c
stillLocked = <- isLocked
if !stillLocked {
break

nsf

unread,
Jun 21, 2010, 1:52:55 PM6/21/10
to golan...@googlegroups.com
On Mon, 21 Jun 2010 10:43:46 -0700 (PDT)
David Leimbach <lei...@gmail.com> wrote:

> For a state machine I think you can leverage closures and message
> passing to get something fairly expressive.

Go's goroutines have some memory overhead (4kb per goroutine, correct
me if I'm wrong). So it's not really a good solution if you have a lot
of small state machines (e.g. state of the game unit AI in a MMORPG
game). But in the MMO case the virtual function tables isn't a good
solution probably also. :D

Anyway, thanks for a suggestion. Maybe in some cases it's ok to use
goroutines and channels.

David Leimbach

unread,
Jun 21, 2010, 2:12:24 PM6/21/10
to golang-nuts
You certainly don't need the goroutine at all in what I posted. You
can instead use global variables, and remove the runstate function and
just use that logic internally to some other loop. I only used
goroutines to show how this might "fit" with the rest of Go, since we
were talking idioms for this language ;-)

The key part I thought was worth mentioning was the use of closures
and the threading of input events or state to the machine as a way to
isolate that code from the actual state changes going on.

Dave

jimmy frasche

unread,
Jun 21, 2010, 2:23:17 PM6/21/10
to David Leimbach, golang-nuts
You could also have a struct for the FSM with the states as private
methods and put the trampoline in a public "run one state with given
input and return the output of that state" method. Then it would be
trivial to wrap it in a goroutine like:

func() {
for {
out <- fsm.Run(<-in)
}
}

when you wanted to use it as a server.

ptolomy23

unread,
Jun 21, 2010, 4:04:43 PM6/21/10
to nsf, golan...@googlegroups.com
What does the interface approach offer over a closure-based one?

Example:

package main

import "fmt"


type Context struct {
next    func(*Context)
name    string
counter int
}

func StateA(self *Context) {
println("state A")
self.counter++
if self.counter > 1 {
self.next = func(s *Context) {
println("Closure state!")
s.next = StateB
}
}
}

func StateB(self *Context) {
println("state B")
}

func StateC(self *Context) {
fmt.Printf("state C, hello %s\n", self.name)
self.next = StateA
}

func main() {
s := &Context{StateC, "nsf", 0}
for i := 0; i < 6; i++ {
s.next(s)

nsf

unread,
Jun 21, 2010, 4:36:50 PM6/21/10
to golan...@googlegroups.com
On Mon, 21 Jun 2010 13:04:43 -0700
ptolomy23 <ptol...@gmail.com> wrote:

> What does the interface approach offer over a closure-based one?

Nothing. You've written the same thing as I did, just using different
language form. Interface after all it's somewhat a table with function
pointers (well, the details are more complicated, but the meaning is
roughly the same). You're on the other hand using function pointers
directly, there is no so much difference.

This is all mostly about syntax. I'm sure I could use state "enum" and
switch statement as well.

Reply all
Reply to author
Forward
0 new messages