5

Given these F# type declarations...

type Message =
    | MessageA
    | MessageB
    | MessageC
    | MessageD

type State = {
    Name:string
    NextStateMap: Map<Message,State>
}

...is there an equally expressive definition of this specific state machine...

let rec state0 = { Name = "0"; NextStateMap = Map.ofList [ (MessageA,state1); (MessageB,state2)] }
    and state1 = { Name = "1"; NextStateMap = Map.ofList [ (MessageB,state3)] }
    and state2 = { Name = "2"; NextStateMap = Map.ofList [ (MessageA,state3)] }
    and state3 = { Name = "3"; NextStateMap = Map.ofList [ (MessageC,state4)] }
    and state4 = { Name = "4"; NextStateMap = Map.ofList [ (MessageD,state5)] }
    and state5 = { Name = "5"; NextStateMap = Map.empty}

...with Python?

Note that via the "rec", we didn't have to do assignments in an order defined by a topological sort... (e.g. state0 is defined in terms of state1, even though state1 is defined later on).

P.S. The option of using strings as state identifiers...

stateMachine = {
   "0" : { "A":"1", "B":"2"},
   "1" : { "B":"3" },
...

...leaves open the case of invalid keys (i.e. invalid message specifiers in the state machine).

ttsiodras
  • 10,602
  • 6
  • 55
  • 71
  • 2
    You're using static typing to create a state machine, Python is dynamically typed. – wheaties Feb 25 '11 at 16:00
  • I am sorry, but having the interpreter check as many things as possible is a wise policy, regardless of the non-statically-typed nature of Python. See the answer by Duncan below. – ttsiodras Feb 25 '11 at 16:10
  • I agree with wheaties. The answer below does nothing to stop the case of invalid keys. After all I can have the dict containing `{message_a: state1, message_b: "anything but a state"}` and the interpreter wouldn't check anything. Because the interpreter _doesn't check anything_ in this case. – Muhammad Alkarouri Feb 27 '11 at 00:48
  • I was not asking about the impossible task of having the type safety of F# - I was asking about the best way to migrate a state machine to Python. When transliterating from F#, one might have a typo: "{message_a: sttate1, ...}" and Python will GET that (as opposed to a "strings-only" approach). What you are saying is: "yes, Duncan's suggestion catches a lot more than the strings approach, but in Python, you can't catch everything". Of course. – ttsiodras Feb 27 '11 at 10:16

2 Answers2

5

In Python I think you'd define the states and then set the map. Pseudo-code like:

state0 = State("0")
state1 = State("1")
... and so on ...
state0.next_states = {message_a: state1, message_b: state2 }
state1.next_states = {message_b: state3}
... and so on ...
Duncan
  • 92,073
  • 11
  • 122
  • 156
0
## a generic state machine framework ###################

class Message(object):
    """
    This represents a message being passed to the
    state machine.
    """
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return "Message(%r)" % self.name
    def __call__(self, smap):
        try:
            return smap[self]
        except KeyError:
            raise Exception("invalid message: %s vs %s"
                            % (self, smap))

class MessageFactory(object):
    """
    Since python doesn't have symbols, this automagically
    creates the messages for you. (It's purely for
    convenience, and you could just as easily instantiate
    each message by hand.
    """
    cache = {}
    def __getattr__(self, name):
        return self.cache.setdefault(name, Message(name))

class StateMachine(object):
    """
    This keeps track of the state, of course. :)
    """
    def __init__(self, state):
        self.state = state
    def __call__(self, msg):
        self.state = self.state(msg)


## how to set it up: ###################################

msg = MessageFactory()
state =\
{
    0 : lambda m: m({ msg.A : state[1],
                      msg.B : state[2] }),
    1 : lambda m: m({ msg.B : state[3] }),
    2 : lambda m: m({ msg.A : state[3] }),
    3 : lambda m: m({ msg.C : state[4] }),
    4 : lambda m: m({ msg.D : state[5] }),
    5 : lambda m: m({ }),
}

## how to use it: ######################################

s = StateMachine(state[0])
s(msg.A)
assert s.state is state[1]
tangentstorm
  • 7,183
  • 2
  • 29
  • 38