2012-06-02 85 views
9

我想在Haskell寫一個小遊戲,並且有相當數量的狀態需要傳遞。我想嘗試隱藏狀態monad使用狀態monad隱藏顯式狀態

現在我遇到了一個問題:功能,採取國家和一個參數很容易寫入工作在狀態單子。但也有一些函數只是將狀態作爲參數(並返回一個修改的狀態,或者可能是其他的)。

在我的代碼的一部分,我有這樣一行:

let player = getCurrentPlayer state 

我想它不會採取狀態,而不是寫

player <- getCurrentPlayerM 

目前,它的實現看起來像這樣

getCurrentPlayer gameState = 
    (players gameState) ! (on_turn gameState) 

它似乎很簡單,使它在國家單體中的工作,通過這樣寫:

getCurrentPlayerM = do state <- get 
         return (players state ! on_turn state) 

然而,這引起ghc的投訴!它說,沒有使用「get」引起的(MonadState GameState m0)實例。我已經重寫了非常相似的功能,但並不是在其國家單子形式無參,等等預感,我重寫這樣的:

getCurrentPlayerM _ = do state <- get 
         return (players state ! on_turn state) 

果然,它的工作原理!但我當然必須將其稱爲getCurrentPlayerM(),並且我覺得這樣做有點愚蠢。傳遞一個論點是我首先想避免的!

一個額外的驚喜:在ghci中尋找它的類型,我得到

getCurrentPlayerM :: MonadState GameState m => t -> m P.Player 

,但如果我嘗試設置,明確在我的代碼,我得到另一個錯誤:「非類型變量參數的約束MonadState GameState m「並提供語言擴展以允許它。我想這是因爲我的GameState是一個類型而不是一個類型類,但是爲什麼它在實踐中被接受了,但是當我嘗試對它進行明確的時候我並沒有更加困惑。

所以總結起來:

  1. 爲什麼我不能寫在單子國家職能零元?
  2. 爲什麼我不能聲明我的解決方法函數實際上具有的類型?

回答

14

問題是你不寫你的函數的類型簽名,並且單態限制適用。

當你寫:

getCurrentPlayerM = ... 

你正在寫一個頂層一元約束值定義沒有類型聲明,所以Haskell編譯將嘗試推斷定義一個類型。然而,單態限制(Literally:single-shape restriction)指出所有具有推斷類型約束的頂級定義必須解析爲具體類型,即它們不能是多態。


要解釋一下我的意思是,採取這種簡單的例子:

pi = 3.14 

在這裏,我們定義pi沒有類型,因此GHC推斷類型Fractional a => a,即「任何類型的a,只要它可以像一個分數一樣對待。「然而,這種類型是有問題的,因爲它意味着pi不是一個常量,即使它看起來像。爲什麼?因爲pi的值將根據我們希望的類型重新計算。

如果我們有(2::Double) + pi,pi將是Double。如果我們有(3::Float) + pi,pi將是Float。每次使用pi時,都必須重新計算(因爲我們無法存儲替代版本的pi,因爲全部是可能的小數類型,我們可以嗎?)。對於簡單的文字3.14這很好,但如果我們想要更多的小數點pi並且使用一種計算它的花式算法呢?我們不希望每次使用pi時都會重新計算它,那麼,我們呢?

這就是爲什麼Haskell報告指出頂級一元類型約束定義必須具有單一類型(單形)以避免此問題。在這種情況下,pi將得到default類型Double。您可以更改默認的數字類型,如果你想使用default關鍵字:

default (Int, Float) 

pi = 3.14 -- pi will now be Float 

在你的情況,但是,你所得到的推斷簽名:

getCurrentPlayerM :: MonadState GameState m => m P.Player 

它的意思是:「對於任何國家monad,存儲GameState s,找回玩家。「然而,由於單態限制的應用,Haskell被迫嘗試通過爲m選擇一個具體類型來使這種類型變爲非多態。然而,它找不到一個,因爲沒有像狀態數字那樣的狀態monad的默認類型,所以它放棄了。

要麼你想給你的功能一個明確的類型簽名:

getCurrentPlayerM :: MonadState GameState m => m P.Player 

...但你必須添加FlexibleContexts的Haskell語言擴展爲它工作,通過在頂部加入這個你文件:

{-# LANGUAGE FlexibleContexts #-} 

或者,您可以明確指定你想要的狀態單子:

getCurrentPlayerM :: State GameState P.Player 

您也可以通過添加擴展名來禁用單態限制;但是,添加類型簽名要好得多。

{-# LANGUAGE NoMonomorphismRestriction #-} 

PS。如果你有一個函數,你的狀態作爲參數,你可以使用:

value <- gets getCurrentPlayer 

你也應該考慮使用LensesState monads,它可以讓你寫的隱性狀態的傳球非常乾淨的代碼。

+1

明確說明我想要哪個monad似乎是正確的解決方案。是的,我已經遇到了記錄中的記錄問題,並且讀了一些關於Lenses的內容(其中包括我在這裏搜索答案的時候,它似乎被推薦了很多!)。謝謝,優秀的解釋! –