2010-07-30 69 views
2

我想編寫一個函數在Haskell 這個計算使用狀態單子平均是我寫的代碼就狀態單子哈斯克爾

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

你應該粘貼錯誤是什麼。 – 2010-07-30 17:10:38

+0

你在命令行輸入什麼命令來運行'getAverage'函數? – 2014-05-12 23:57:04

回答

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 monad中運行,所以它的類型簽名不正確,因爲它無法評估爲非單目類型。

但是,除此之外,您的代碼還有其他問題,即使在解決該特定問題後也不會編譯。首先一些風格問題,使其更易於閱讀:

  • getAverage有一個未使用的參數,按理說它是在State單子,這並沒有真正意義反正值。
  • 使用do符號通常比使用(>>=)和lambda表達更明確,特別是對於像State之類的東西。
  • 第二行的縮進令人困惑,因爲inlet一致,即裏面的這個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即可,fstget更直接。

所以現在我們有這樣的事情:

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 

編輯:因爲我忘記了實際將結果從單子中取出的小細節,用替換在Travis Brown的getAverages函數將讓它在我的代碼上面工作。

+0

@camccann - 我如何從命令行運行它?我已經將它加載到ghci中,但我不確定getAverage的呼叫應該是什麼 – 2014-05-12 23:50:05

+0

@SamHeather:您需要使用'runState'或類似的函數,例如'runState(getAverage 1)(2,3)'。在這裏查看關於monad狀態的文檔:http://hackage.haskell.org/package/mtl-2.2.0.1/docs/Control-Monad-State-Lazy.html如果您有其他問題,那麼您可能會有作爲一個新問題發佈更多的運氣。 – 2014-05-13 13:38:40

3

注意:camccann的答案比我的好,但是我的方法稍有不同,並給出了一個如何評估狀態monad的例子,所以我將它留在這裏作爲參考。


我們就可以開始嘗試通過除去getAverage類型簽名和參數(c),以找出問題不會出現在函數:

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

這還不編譯,因爲我們試圖put東西沒有正確的類型:s1Double,而不是MyState。這是很容易可以解決的:

getAverage s=get >>= \s0 -> let [email protected](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 

DoubleFractional一個實例,State MyStateMonadState (Double, Double)一個實例,所以我們可以用非常相似,你的原始類型的東西getAverage

getAverage :: Double -> State MyState Double 

該功能並沒有真正「得到」平均:它更新它添加一個新的值之後,讓我們適當將其重命名:

updateAverage :: Double -> State MyState Double 
updateAverage s=get >>= \s0 -> let [email protected](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單子有用你總是不得不使用evalState(或密切相關的runStateexecState)。

+2

我不認爲會幫助提問者,但您可能會喜歡:請注意,沒有流量控制依賴於狀態值?這就是這個*不需要monad *的跡象,因此你的版本可以被重寫爲'updateAverage s = modify(media s)*>(fst <$> get)'。同樣,'mapM'可以概括爲'traverse',它通過任何'Traversable'對任何'Applicative'做同樣的事情。 – 2010-07-30 18:21:11