I am trying to create a simple state machine that changes color state when input is valid:
Red
-> Green
-> Blue
-> Red
...
I want to be able to explicitly define the state transitions. After reading What is indexed monad? and Stephen Diehl's What I Wish I Knew, the indexed monad seems to be what I need. So far I have the following code:
import Prelude hiding ( return )
newtype IState i o a = IState { runIState :: i -> (a, o) }
execIState :: IState i o a -> i -> o
execIState st i = snd $ runIState st i
return :: a -> IState s s a
return a = IState $ \s -> (a,s)
put :: o -> IState i o ()
put o = IState $ const ((), o)
data Red = Red
data Green = Green
data Blue = Blue
blueToRed :: IState Blue Red ()
blueToRed = put Red
redToGreen :: IState Red Green ()
redToGreen = put Green
greenToBlue :: IState Green Blue ()
greenToBlue = put Blue
newtype Color a = Color a
colorChange
:: Color a
-> Bool
-> Color a
colorChange s@(Color c) valid = Color $ flip execIState c $ case s of
(Color Red) | valid -> redToGreen
(Color Green) | valid -> greenToBlue
(Color Blue) | valid -> blueToRed
_ -> return ()
This code gives the error:
Couldn't match type `Blue' with `Green'
Expected type: IState a Green ()
Actual type: IState Green Blue ()
* In the expression: greenToBlue
In a case alternative: (Color Green) | valid -> greenToBlue
In the second argument of `($)', namely
`case s of
(Color Red) | valid -> redToGreen
(Color Green) | valid -> greenToBlue
(Color Blue) | valid -> blueToRed
_ -> return ()'
|
39 | (Color Green) | valid -> greenToBlue
Couldn't match type `Red' with `Green'
Expected type: IState a Green ()
Actual type: IState Blue Red ()
* In the expression: blueToRed
In a case alternative: (Color Blue) | valid -> blueToRed
In the second argument of `($)', namely
`case s of
(Color Red) | valid -> redToGreen
(Color Green) | valid -> greenToBlue
(Color Blue) | valid -> blueToRed
_ -> return ()'
|
40 | (Color Blue) | valid -> blueToRed
I understand that the types Red
, Green
, and Blue
are different. But the errors do not make sense to me, why would the compiler expect IState a Green ()
when greenToBlue :: IState Green Blue ()
? It seems to me it is expecting all types to "match" the first case
pattern redToGreen
. How do I work around this to create my state transfer function? The "What is indexed monad?" post used GADTs, so I thought maybe that would help, but I could not get the example in that post working and I have not used GADTs before, just read about them.
Note this is very simple for debugging and learning purposes, I plan to use this when complexity of my FSMs increase.
Clarification: I want the compiler to give an error if the state transfer function does not preserve the state machine structure. Say I define the state machine structure as: Red
-> Green
-> Blue
-> Red
... but if I accidentally change my colorChange
function so Red
-> Blue
, the compiler should issue an error as this violates the state machine structure where Green
must follow Red
.