Skip to content

Instantly share code, notes, and snippets.

@smurphy8
Last active April 13, 2018 01:18
Show Gist options
  • Save smurphy8/b0c8040284d3b874e6c5 to your computer and use it in GitHub Desktop.
Save smurphy8/b0c8040284d3b874e6c5 to your computer and use it in GitHub Desktop.
Using IxMonad to create an IO StateMachine

This is my short example of creating a an Indexed State Monad

Indexed State Monads are different from the standard state Monad because the type of the state is transformable. This is awesome and really nice for creating statemachines that prohibit invalid transitions at the type level.

Here is the thing I read about [Indexed Monads] (http://blog.sigfpe.com/2009/02/beyond-monads.html)

and here is what I put together

{-# LANGUAGE OverloadedStrings , NoImplicitPrelude #-}

module StateMachine where
import Control.Monad.Indexed
import CorePrelude

Data Types

I am going to be making a simple state machine first with no IO then I will re-do it to use IO

The states in question are Those that might be in an alarm, this is something I have to do a in production code so I figure it might be something that comes up for others

Valid transitions are that the system has to call out between Trip and Clear.

Real alarm systems have side effects and are terrible... but haskell is nice for sorting through it all

data Trip = Trip
 deriving (Show)

data Clear = Clear
 deriving (Show)

data Calling = Calling
 deriving (Show)

Straight from the link above, here are the states applied to the IxMonad Class

newtype IxState s1 s2 a = IxState { runIxState :: s1 -> (a, s2) }

instance IxFunctor (IxState) where 
  imap f ixSt = IxState (\st -> let (a,st') = (runIxState ixSt st)
                                in (f a, st'))
                                              
instance IxPointed IxState where 
    ireturn a = IxState (\st -> (a, st))

instance IxApplicative IxState where 
    iap ixStFcn ixSt = IxState (\st1 -> let (fAB,st2) = (runIxState ixStFcn st1)
                                            (a,st3)   = runIxState ixSt st2
                                        in  (fAB a,st3))

instance IxMonad IxState where 
    ibind atoIxStFcn ixStA = IxState (\st1 -> let (a,st2) = runIxState ixStA st1
                                              in  runIxState (atoIxStFcn a) st2)


testIx1 :: IxState Clear Trip (Text)
testIx1 = IxState (\(Clear) -> (toTripAction, Trip))

testIx2 :: IxState Trip Calling (Text)
testIx2 = IxState (\(Trip) -> (toCallAction, Calling))

testIx3 :: IxState Calling Clear (Text)
testIx3 = IxState (\(Calling) -> (toClearAction, Clear))


transforms :: IxState Clear Calling Text
transforms = testIx1 >>>= (\_ -> testIx2 )


toTripAction :: Text 
toTripAction = "alarm tripping"

toClearAction :: Text
toClearAction = "alarmClearing"  

toCallAction :: Text
toCallAction ="Calling User  "

State machine with IO

It is a neat process to re-factor this IxMonad into one with a regular Monad embedded

data IxStateM m s1 s2 a = IxStateM { 
     runIxStateM :: s1 -> m (a,s2) 
    }

instance (Monad m) => IxFunctor (IxStateM m) where 
  imap f ixSt = IxStateM {
                 runIxStateM  = (\ st -> do
                                     (b,st') <- runIxStateM ixSt st
                                     return (f b, st')) }
                                              
instance (Monad m ) => IxPointed (IxStateM m) where 
    ireturn a = IxStateM {
                 runIxStateM = (\st -> return (a, st) )                    
                }


instance (Monad m) => IxApplicative (IxStateM m) where 
    iap ixStFcn ixSt = IxStateM {
                         runIxStateM = (\st1 -> do
                                          (fAB,st2) <- runIxStateM ixStFcn st1
                                          (a,st3)   <- runIxStateM ixSt st2
                                          return (fAB a,st3))
                       }



instance (Monad m ) => IxMonad (IxStateM m) where 
    ibind atoIxStFcn ixStA = IxStateM {
                               runIxStateM = (\st1 -> do
                                                (a,st2) <- runIxStateM ixStA st1
                                                runIxStateM (atoIxStFcn a) st2)
                             }

Some IO to run, notice that each one of them has an initial and a final state
How cool is this! 
someStaticState :: (Show s) => s ->  Int -> IO String
someStaticState s i = print s >> print i >>  return "Test IO"

exClear :: Int -> IxStateM IO Clear Calling String
exClear init = IxStateM (\Clear -> do                        
                        a <- someStaticState Clear init
                        return $ (a,Calling))

exTripCalling :: Int -> IxStateM IO Calling Trip String
exTripCalling init = IxStateM (\Calling -> do                        
                        a <- someStaticState Calling init
                        return $ (a,Trip))

exTrip :: Int -> IxStateM IO Trip Calling String
exTrip init = IxStateM (\Trip -> do                        
                        a <- someStaticState Trip init
                        return $ (a,Calling))


exClearCalling :: Int -> IxStateM IO Calling Clear String
exClearCalling init = IxStateM (\Calling -> do                        
                        a <- someStaticState Calling init
                        return $ (a,Clear))

sampleCycle :: IxStateM IO Clear Clear String
sampleCycle = (exClear 1)  
              >>>= (\_  -> exTripCalling 2) 
              >>>= (\_  -> exTrip 3)
              >>>= (\_  -> exClearCalling 4 )


exIxStateM :: IO (String, Clear)
exIxStateM = runIxStateM sampleCycle Clear

sampleInvaldiCycle = (exClear 1)  
                     >>>= (\str  -> exTripCalling 2) 
                     >>>= (\str -> exClearCalling 4 )


-- > Couldn't match type `Calling' with `Trip'
-- > Expected type: IxStateM IO Trip Clear String
-- > Actual type: IxStateM IO Calling Clear String
-- > In the return type of a call of `exClearCalling'
-- > In the expression: exClearCalling 4
-- > In the second argument of `(>>>=)', namely
-- >   `(\ str -> exClearCalling 4)'

sampleCycle = (exClear 1)  
              >>>= (\str  -> exTripCalling 2) 
              >>>= (\str  -> exTrip 3)
              >>>= (\str -> exClearCalling 4 )



-- > runIxStateM sampleCycle Clear
-- > Clear
-- > Calling
-- > Trip
-- > Calling
-- > ("Test IO",Clear)

Anyway, Indexed Monads are super cool, you can make what can be really easy to obfuscate jump up the top of a type definition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment