haskellstatemonads

State Monad in Haskell . How to adapt functors, applicatives and monads to characteristics and Die Rolls


I'm trying to use State Monad in a personal section of code in order to have a more natural code . The problem and the code section are the following ones . I'm trying to ask interactively a user to assign the different results of a die roll to different characteristics .

generateCharacteristics :: IO ()
generateCharacteristics = do
  dieResults <- generateCharacteristicsIntroMessage
  (strength, dieResultStrength) <- askStrengthComp dieResults
  (agility, dieResultAgility) <- askAgilityComp dieResultsStrength
  (constitution, dieResultConstitution) <- askConstitutionComp dieResultAgility  

newtype StrengthValue = StrengthValue Int
newtype AgilityValue = AgilityValue Int
...

askStrengthComp :: [Int] -> IO(StrengthValue, [Int])
-- ask to pick a value from the input list of die rolls, 
-- remove the value from the input list of die rolls and returns 
-- a tuple with StrengthValue and resulting list of die rolls
...

askAgilityComp :: [Int] -> IO(AgilityValue, [Int])
-- Same as askStrengthComp but for Agility

...

As you can see , the dieResults are moved through the 'do' actions, and I want to have a State Monad where

DieRolls -> (StrengthValue, newDieRolls)

So, the state monad can handle internally the action of removing the chosen die roll assigned to the characteristic .

Following amazing Graham Hutton book, I don´t have clear how to adapt the creation of the functor and applicative functions

type StateMerp = [Int]
newtype STMerp a = SMerp (StateMerp -> (a,StateMerp))

appMerp :: STMerp a -> StateMerp -> (a, StateMerp)
appMerp (SMerp st) x = st x

instance Functor STMerp where
...

instance Applicative STMerp where
...

instance Monad STMerp where
... 

For the sake of simplicity, in the part of State, I have removed IO . Just learning about this monad, so I think what I´ḿ trying can be done . Could you help me in the creation of the state monad so I can get rid off moving the die rolls time after time ? THanks .


Solution

  • This is how I rewrote State following the book, Haskell Programming from First Principles (Allan & Moronuki, 2016).

    State, runState, Functor

    newtype State s a = State { runState :: s -> (a,s) }
    
    instance Functor (State s) where
      fmap :: (a -> b) -> State s a -> State s b
      fmap f (State g) = State (fmap (\(a,s) -> (f a, s)) $ runState (State g))
    
    ghci> let s0 = State (\s -> (2,s))
    ghci> let s1 = fmap (+1) s0
    ghci> runState s1 0
    (3,0)
    

    Applicative

    instance Applicative (State s) where
      pure :: a -> State s a
      pure a = State $ \s -> (a,s)
    
      (<*>) :: State s (a -> b) -> State s a -> State s b
      (State sab) <*> (State sa) = State (\s -> let (a, s0)  = sa s
                                                    (ab, s1) = sab s
                                                in (ab a, s1))
    
    ghci> let sab = State (\s -> ((+1), s))
    ghci> let sa  = State (\s -> (2, s))
    ghci> let s   = sab <*> sa
    ghci> runState s 0
    (3,0)
    

    Monad

    instance Monad (State s) where
      return = pure
      (>>=) :: State s a -> (a -> State s b) -> State s b
      (>>=) (State f) g = State (\s -> let (a,s') = f s
                                       in runState (g a) s')
    
    ghci> let sa = State (\s -> (2, s))
    ghci> let g = \a -> State (\s -> (a+1, s))
    ghci> let sb = sa >>= g
    ghci> runState sb 0
    (3,0)
    

    This is how the standard 'State' works. I sense that you might be trying to do something different. Hopefully this will give you a start.