I implemented a simple state machine in Python:
import time
def a():
print \"a()\"
return b
def b():
print \"b()\"
return c
def c():
print
The problem with your Haskell code is, that type
only introduces a synonym, which is quite similar to what typedef
in C does. One important restriction is, that the expansion of the type must be finite, you can't give a finite expansion of your state machine. A solution is using a newtype
: A newtype
is a wrapper that does only exist for the type checker; there is absolutely zero overhead (excluded stuff that occurs because of generalization that can't be removed). Here is your signature; it typechecks:
newtype FN = FN { unFM :: (IO FN) }
Please note, that whenever you want to use an FN
, you have to unpack it first using unFN
. Whenever you return a new function, use FN
to pack it.
As usual, despite the great answers already present, I couldn't resist trying it out for myself. One thing that bothered me about what is presented is that it ignores input. State machines--the ones that I am familiar with--choose between various possible transitions based on input.
data State vocab = State { stateId :: String
, possibleInputs :: [vocab]
, _runTrans :: (vocab -> State vocab)
}
| GoalState { stateId :: String }
instance Show (State a) where
show = stateId
runTransition :: Eq vocab => State vocab -> vocab -> Maybe (State vocab)
runTransition (GoalState id) _ = Nothing
runTransition s x | x `notElem` possibleInputs s = Nothing
| otherwise = Just (_runTrans s x)
Here I define a type State
, which is parameterized by a vocabulary type vocab
. Now let's define a way that we can trace the execution of a state machine by feeding it inputs.
traceMachine :: (Show vocab, Eq vocab) => State vocab -> [vocab] -> IO ()
traceMachine _ [] = putStrLn "End of input"
traceMachine s (x:xs) = do
putStrLn "Current state: "
print s
putStrLn "Current input: "
print x
putStrLn "-----------------------"
case runTransition s x of
Nothing -> putStrLn "Invalid transition"
Just s' -> case s' of
goal@(GoalState _) -> do
putStrLn "Goal state reached:"
print s'
putStrLn "Input remaining:"
print xs
_ -> traceMachine s' xs
Now let's try it out on a simple machine that ignores its inputs. Be warned: the format I have chosen is rather verbose. However, each function that follows can be viewed as a node in a state machine diagram, and I think you'll find the verbosity to be completely relevant to describing such a node. I've used stateId
to encode in string format some visual information about how that state behaves.
data SimpleVocab = A | B | C deriving (Eq, Ord, Show, Enum)
simpleMachine :: State SimpleVocab
simpleMachine = stateA
stateA :: State SimpleVocab
stateA = State { stateId = "A state. * -> B"
, possibleInputs = [A,B,C]
, _runTrans = \_ -> stateB
}
stateB :: State SimpleVocab
stateB = State { stateId = "B state. * -> C"
, possibleInputs = [A,B,C]
, _runTrans = \_ -> stateC
}
stateC :: State SimpleVocab
stateC = State { stateId = "C state. * -> A"
, possibleInputs = [A,B,C]
, _runTrans = \_ -> stateA
}
Since the inputs don't matter for this state machine, you can feed it anything.
ghci> traceMachine simpleMachine [A,A,A,A]
I won't include the output, which is also very verbose, but you can see it clearly moves from stateA
to stateB
to stateC
and back to stateA
again. Now let's make a slightly more complicated machine:
lessSimpleMachine :: State SimpleVocab
lessSimpleMachine = startNode
startNode :: State SimpleVocab
startNode = State { stateId = "Start node. A -> 1, C -> 2"
, possibleInputs = [A,C]
, _runTrans = startNodeTrans
}
where startNodeTrans C = node2
startNodeTrans A = node1
node1 :: State SimpleVocab
node1 = State { stateId = "node1. B -> start, A -> goal"
, possibleInputs = [B, A]
, _runTrans = node1trans
}
where node1trans B = startNode
node1trans A = goalNode
node2 :: State SimpleVocab
node2 = State { stateId = "node2. C -> goal, A -> 1, B -> 2"
, possibleInputs = [A,B,C]
, _runTrans = node2trans
}
where node2trans A = node1
node2trans B = node2
node2trans C = goalNode
goalNode :: State SimpleVocab
goalNode = GoalState "Goal. :)"
The possible inputs and transitions for each node should require no further explanation, as they are verbosely described in the code. I'll let you play with traceMachine lessSipmleMachine inputs
for yourself. See what happens when inputs
is invalid (does not adhere to the "possible inputs" restrictions), or when you hit a goal node before the end of input.
I suppose the verbosity of my solution sort of fails what you were basically asking, which was to cut down on the cruft. But I think it also illustrates how descriptive Haskell code can be. Even though it is very verbose, it is also very straightforward in how it represents nodes of a state machine diagram.
Iit's not hard to make state machines in Haskell, once you realize that they are not monads! A state machine like the one you want is an arrow, an automaton arrow to be exact:
newtype State a b = State (a -> (b, State a b))
This is a function, which takes an input value and produces an output value along with a new version of itself. This is not a monad, because you cannot write join
or (>>=)
for it. Equivalently once you have turned this into an arrow you will realize that it's impossible to write an ArrowApply
instance for it.
Here are the instances:
import Control.Arrow
import Control.Category
import Prelude hiding ((.), id)
instance Category State where
id = State $ \x -> (x, id)
State f . State g =
State $ \x ->
let (y, s2) = g x
(z, s1) = f y
in (z, s1 . s2)
instance Arrow State where
arr f = let s = State $ \x -> (f x, s) in s
first (State f) =
State $ \(x1, x2) ->
let (y1, s) = f x1
in ((y1, x2), first s)
Have fun.
You can get the same effect in C as in Python code,- just declare that functions return (void*)
:
#include "stdio.h"
typedef void* (*myFunc)(void);
void* a(void);
void* b(void);
void* c(void);
void* a(void) {
printf("a()\n");
return b;
}
void* b(void) {
printf("b()\n");
return c;
}
void* c(void) {
printf("c()\n");
return a;
}
void main() {
void* state = a;
while (1) {
state = ((myFunc)state)();
sleep(1);
}
}
The Python code you posted will be transformed into a recursive function, but it will not be tail call optimized because Python has no tail call optimization, so it will stack overflow at some point. So the Python code is actually broken, and would take more work to get it as good as the Haskell or C versions.
Here is an example of what I mean:
so.py:
import threading
stack_size_bytes = 10**5
threading.stack_size(10**5)
machine_word_size = 4
def t1():
print "start t1"
n = stack_size_bytes/machine_word_size
while n:
n -= 1
print "done t1"
def t2():
print "start t2"
n = stack_size_bytes/machine_word_size+1
while n:
n -= 1
print "done t2"
if __name__ == "__main__":
t = threading.Thread(target=t1)
t.start()
t.join()
t = threading.Thread(target=t2)
t.start()
t.join()
shell:
$ python so.py
start t1
done t1
start t2
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 530, in __bootstrap_inner
self.run()
File "/usr/lib/python2.7/threading.py", line 483, in run
self.__target(*self.__args, **self.__kwargs)
File "so.py", line 18, in t2
print "done t2"
RuntimeError: maximum recursion depth exceeded