问题
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
回答1:
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 theState
monad, which doesn't really make sense anyway.- Using the
do
notation is usually clearer than using(>>=)
and lambdas, especially for something likeState
. - The indentation of the second line is confusing, since the
in
goes with thelet
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 thanget
ting andput
ting, just use themodify
function.- The return value is the first part of the state value, so just
fmap
ingfst
overget
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.
回答2:
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 Double
s, 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
).
来源:https://stackoverflow.com/questions/3373794/state-monad-haskell