state monad haskell

2019-07-14 08:48发布

I want to write a function for calculating the average using the State Monad in haskell this is the code I wrote as far

import Control.Monad.State
type MyState = (Double,Double)
media s (a,n)= ((a*n+s)/(n+1),n+1)

getAverage:: Double ->State MyState  s1-> Double
getAverage s c=get >>= \s0 -> let (x,s1) =media s s0
            in put s1 >> return x

I got this error when compile in GHCI, and I stuck there can you help me to understand what is wrong, thank you in advance

2条回答
Root(大扎)
2楼-- · 2019-07-14 09:01

Note: camccann's answer is better than mine, but mine takes a slightly different approach and gives an example of how to evaluate the state monad, so I'm leaving it here for reference.


We can start trying to figure out the problem by removing the type signature for getAverage and the argument (c) that doesn't appear in the function:

getAverage s=get >>= \s0 -> let (x,s1) =media s s0
            in put s1 >> return x

This still doesn't compile, because we're trying to put something that doesn't have the right type: s1 is a Double, not a MyState. This is easily fixable:

getAverage s=get >>= \s0 -> let s1@(x,_) =media s s0
            in put s1 >> return x

We could also leave the let pattern unchanged and just say put (x,s1): I'm doing it this way instead so that our s1 has the same type as s0.

This compiles, so now we can fix the type signature. If we ask GHCi for the type, it returns the following:

getAverage :: (Fractional t, MonadState (t, t) m) => t -> m t

Double is an instance of Fractional, and State MyState is an instance of MonadState (Double, Double), so we can use something very similar to your original type for getAverage:

getAverage :: Double -> State MyState Double

This function doesn't really "get" the average: it updates it after adding a new value, so let's rename it appropriately:

updateAverage :: Double -> State MyState Double
updateAverage s=get >>= \s0 -> let s1@(x,_) =media s s0
            in put s1 >> return x

Now we can define a getAverages function that takes a list of Doubles, runs them through updateAverage, and returns a list of the intermediate averages at each step:

getAverages :: [Double] -> [Double]
getAverages ss = evalState (mapM updateAverage ss) (0, 0)

This does what we'd expect:

*Main> getAverages [1..10]
[1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5]

Note that to do anything useful with the State monad you'll always have to use evalState (or the closely related runState and execState).

查看更多
我命由我不由天
3楼-- · 2019-07-14 09:20

The code you provided gives this error:

Couldn't match expected type `Double'
       against inferred type `m Double'
In the expression:
      get >>= \ s0 -> let (x, s1) = ... in put s1 >> return x
In the definition of `getAverage':
    getAverage s c = get >>= \ s0 -> let ... in put s1 >> return x

All this means is that the type resulting from the expression ("inferred") disagrees with the type signature ("expected"). In this case, getAverage operates in the State monad, so it's the type signature that's incorrect, as it can't evaluate to a non-monadic type.

Your code has other problems besides that, however, and won't compile even after fixing that particular issue. First a few stylistic issues to make it more readable:

  • getAverage has an unused parameter, which is supposedly a value in the State monad, which doesn't really make sense anyway.
  • Using the do notation is usually clearer than using (>>=) and lambdas, especially for something like State.
  • The indentation of the second line is confusing, since the in goes with the let that's inside the lambda.

Making those changes we have this:

getAverage s = do
    s0 <- get
    let (x, s1) = media s s0
    put s1 
    return x

...which makes it easier to spot the next error: The second argument of media is a 2-tuple, and s1 is just a single number, but you're trying to use both for the state value. Probably what you wanted was to set the state to (x, s1), but return only x.

getAverage s = do
    s0 <- get
    let (x,s1) = media s s0
    put (x,s1)
    return x

This compiles just fine, but still needs some tidying:

  • media needs to update the entire state value, so rather than getting and putting, just use the modify function.
  • The return value is the first part of the state value, so just fmaping fst over get is more straightforward.

So now we have something like this:

media :: Double -> MyState -> MyState
media s (a, n) = ((a * n + s) / (n + 1), n + 1)

getAverage:: Double -> State MyState Double
getAverage s = do
    modify (media s)
    fmap fst get

We can also note that getAverage is kind of doing two different things, and split it into separate functions:

updateAverage:: Double -> State MyState ()
updateAverage s = modify (media s)

currentAverage :: State MyState Double
currentAverage = fmap fst get

getAverage:: Double -> State MyState Double
getAverage s = updateAverage s >> currentAverage

Edit: And since I forgot about the minor detail of actually getting the results back out of the monad, replacing updateAverage with getAverage in Travis Brown's getAverages function will let it work on my code above.

查看更多
登录 后发表回答