我想编写一个函数来计算使用haskell中的State Monad的平均值 这是我到目前为止编写的代码
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
我在GHCI编译时遇到了这个错误,我就陷入了困境 你可以帮我理解什么是错的,提前谢谢你
答案 0 :(得分:6)
您提供的代码会出现此错误:
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
所有这些意味着表达式(“推断”)产生的类型与类型签名(“预期”)不一致。在这种情况下,getAverage
在State
monad中运行,因此它的类型签名不正确,因为它无法评估为非monadic类型。
除此之外,您的代码还有其他问题,即使在修复该特定问题后也无法编译。首先是一些风格问题,使其更具可读性:
getAverage
有一个未使用的参数,据说是State
monad中的一个值,无论如何都没有意义。do
表示法通常比使用(>>=)
和lambdas更清晰,特别是State
之类的内容。in
与let
内的内部 lambda一起使用。进行这些更改我们有:
getAverage s = do
s0 <- get
let (x, s1) = media s s0
put s1
return x
...这样可以更容易地发现下一个错误:media
的第二个参数是2元组,而s1
只是一个数字,但您正在尝试使用两者都是为了国家价值。可能你想要的是将状态设置为(x, s1)
,但只返回x
。
getAverage s = do
s0 <- get
let (x,s1) = media s s0
put (x,s1)
return x
这个编译很好,但仍需要一些整理:
media
需要更新整个州的值,而不是get
和put
,只需使用modify
功能。fmap
fst
get
超过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
更为直接。所以现在我们有这样的事情:
getAverage
我们还可以注意到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
有两种不同的东西,并将它分成不同的函数:
updateAverage
修改:因为我忘记了实际将结果从monad中取回的细节,在Travis Brown的{{1}中将getAverage
替换为getAverages
函数将使它适用于我上面的代码。
答案 1 :(得分:3)
注意:camccann的答案比我的好,但是我的方法略有不同,并给出了如何评估状态monad的示例,所以我将它留在这里作为参考。
我们可以通过删除getAverage
的类型签名和函数中没有出现的参数(c
)来开始尝试解决问题:
getAverage s=get >>= \s0 -> let (x,s1) =media s s0
in put s1 >> return x
这仍然无法编译,因为我们正在尝试put
某些类型不正确的内容:s1
是Double
,而不是MyState
}。这很容易解决:
getAverage s=get >>= \s0 -> let s1@(x,_) =media s s0
in put s1 >> return x
我们也可以保持let
模式不变,只说put (x,s1)
:我这样做是为了让我们的s1
与s0
具有相同的类型
这个编译,现在我们可以修复类型签名。如果我们向GHCi询问类型,则返回以下内容:
getAverage :: (Fractional t, MonadState (t, t) m) => t -> m t
Double
是Fractional
的一个实例,State MyState
是MonadState (Double, Double)
的一个实例,因此我们可以使用与getAverage
的原始类型非常相似的内容}:
getAverage :: Double -> State MyState Double
这个函数并没有真正“获得”平均值:它在添加新值后更新它,所以让我们适当地重命名它:
updateAverage :: Double -> State MyState Double
updateAverage s=get >>= \s0 -> let s1@(x,_) =media s s0
in put s1 >> return x
现在我们可以定义一个getAverages
函数,它接受Double
的列表,通过updateAverage
运行它们,并返回每个步骤的中间平均值列表:
getAverages :: [Double] -> [Double]
getAverages ss = evalState (mapM updateAverage ss) (0, 0)
这就是我们所期望的:
*Main> getAverages [1..10]
[1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5]
请注意,要对State
monad执行任何有用的操作,您必须始终使用evalState
(或密切相关的runState
和execState
)。