2

我想写一个函数来使用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 中编译时遇到了这个错误,我卡在那里你能帮我理解什么是错的,提前谢谢你

4

2 回答 2

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

这意味着从表达式(“推断”)产生的类型与类型签名(“预期”)不一致。在这种情况下,getAverageState单子中运行,因此类型签名不正确,因为它无法评估为非单子类型。

但是,除此之外,您的代码还有其他问题,即使在修复了该特定问题后也无法编译。首先是一些文体问题,使其更具可读性:

  • getAverage有一个未使用的参数,它应该是Statemonad 中的一个值,无论如何这没有任何意义。
  • 使用do符号通常比使用(>>=)和 lambda 更清晰,尤其是对于State.
  • 第二行的缩进令人困惑,因为它in与lambda内部let的that一致。

进行这些更改,我们有:

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需要更新整个 state 值,所以不用getting 和putting,直接使用modify函数即可。
  • 返回值是状态值的第一部分,所以直接fmaping fstoverget更直接。

所以现在我们有这样的东西:

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

编辑:由于我忘记了实际从 monad 中获取结果的小细节,替换updateAveragegetAverageTravis Brown 的getAverages函数将让它在我上面的代码上工作。

于 2010-07-30T17:46:45.933 回答
3

注意:camccann 的答案比我的要好,但我的方法略有不同,并给出了如何评估状态单子的示例,因此我将其留在这里以供参考。


getAverage我们可以通过删除函数的类型签名和参数 ( )来尝试找出问题c所在:

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

这仍然无法编译,因为我们正在尝试put一些没有正确类型的东西:s1是 a Double,而不是 a 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函数,它接受一个Doubles 列表,运行它们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]

请注意,要对Statemonad 做任何有用的事情,您总是必须使用evalState(或密切相关的runStateand execState)。

于 2010-07-30T18:01:57.073 回答