Search code examples
haskellstate-monad

Can't match expected type and stuck on assignment about State Monads


For an assignment for FP we have to write a function that runs a state monadic computation given an initial state, and then returns the computed value and the number of operations counted.

Counts looks like this:

data Counts = Counts {
    binds   :: Int,
    returns :: Int,
    gets    :: Int,
    puts    :: Int
} deriving (Eq, Show)

Where oneBind = Counts 1 0 0 0 (for example). There was also an mempty and <*> defined, but I wasn't able to use "mempty" instead of "Counts 0 0 0 0" with initCounts.

The States are defined as:

newtype State' s a = State' { runState' :: (s, Counts) -> (a, s, Counts) }

So far this is what I have got, but I've been stuck at about the same level for a few hours now.

run :: State' s a -> s -> (a, Counts)
run s ns = do
    initState <- return ns
    initCounts <- return (Counts 0 0 0 0)
    newState <- return (runState' s (initState, initCounts))
    newCounts <- return (runState' (retCounts newState) (newState, initCounts))
    let st = let (a,_,_) = newState
             in a
    let count = let (c,_,_) = newCounts
                in c
    return (count)


retCounts :: State' s a -> State' s Counts
retCounts st = State' (\ (s, count) -> (calcCounts st, s, count))

calcCounts :: State' s a -> Counts
calcCounts st = undefined

I assume I have to use pattern matching in calcCounts to somehow actually count all the operators/functions, but right now I'm getting a type matching error:

Assignment4.hs:236:47:
Couldn't match expected type ‘State' (a, s, Counts) a0’
            with actual type ‘(a, s, Counts)’
Relevant bindings include
  newState :: (a, s, Counts) (bound at Assignment4.hs:235:5)
  initState :: s (bound at Assignment4.hs:233:5)
  ns :: s (bound at Assignment4.hs:232:7)
  s :: State' s a (bound at Assignment4.hs:232:5)
  run :: State' s a -> s -> (a, Counts)
    (bound at Assignment4.hs:232:1)
In the first argument of ‘retCounts’, namely ‘newState’
In the first argument of ‘runState'’, namely ‘(retCounts newState)’

If I could get any help on how I could solve this type error and some hints to go from here, it would be highly appreciated.

PS: I realize it might be a good idea to rename calcCounts to something like calcFunctions

[EDIT: I'm also getting a different error when I work around this one by supplying a dummy value:

Assignment4.hs:233:5:
No instance for (Monad ((,) a)) arising from a do statement
In a stmt of a 'do' block: initState <- return ns
In the expression:
  do { initState <- return ns;
       initCounts <- return (Counts 0 0 0 0);
       newState <- return (runState' s (initState, initCounts));
       newCounts <- return (runState' retCounts (newState, initCounts));
       .... }
In an equation for ‘run’:
    run s ns
      = do { initState <- return ns;
             initCounts <- return (Counts 0 0 0 0);
             newState <- return (runState' s (initState, initCounts));
             .... }

]


Solution

  • There are a few problems here.

    There was also an mempty and <*> defined...

    Do you mean <> = mappend? <*> is the application operator for applicative functors.

    run :: State' s a -> s -> (a, Counts)
    run s ns = do
        initState <- return ns
        initCounts <- return (Counts 0 0 0 0)
        newState <- return (runState' s (initState, initCounts))
        newCounts <- return (runState' (retCounts newState) (newState, initCounts))
        let st = let (a,_,_) = newState
                 in a
        let count = let (c,_,_) = newCounts
                    in c
        return (count)
    

    Firstly, think about what the type of your do block is. It has the same type as run s ns, which according to your type signature for run is (a, Counts). As you know, do notation only works with monads. (a, Counts) (or more accurately ((,) a)) is not a monad, which is one of the reasons GHC is getting confused.

    Next, you should note that in a do block:

    initState <- return ns
    

    is identical to

    let initState = ns
    

    If we rewrite run to reflect this, we get the following:

    run s ns = do
        let initState = ns
            initCounts = Counts 0 0 0 0
            newState = runState' s (initState, initCounts)
            newCounts = runState' (retCounts newState) (newState, initCounts)
        let st = let (a,_,_) = newState
                 in a
        let count = let (c,_,_) = newCounts
                    in c
        return count
    

    Now we can see the next big problem: you aren't actually binding any monadic variables in your do block! In fact, by manually using newState etc you are circumventing the entire point of a state monad -- to keep track of state for us.

    The fact is that all this is not so complicated as you might believe; we don't actually even need to use do notation. I advise that you think about this a little more before looking at the text below, to see if you can find the solution. As a hint: the function can be written simply in one line.


    Here's how I might write run:

    run :: State' s a -> s -> (a, Counts)
    run s ns = let (a, _, counts) = runState' s (ns, Counts 0 0 0 0) in (a, counts)