2017-07-18 84 views
3

我在學習Haskell。我試圖編寫一個包含「全局狀態」的程序:Vars。每次調用函數時,我都想更改狀態的一個組成部分(例如var1)。該變化可以是對組件的簡單功能(例如+4)。此外,它還打印出已更改的組件。這是我迄今爲止所做的(但我被卡住了)。編輯:運行代碼後,我想查看最新版本的全局狀態。Haskell:Monad變形金剛和全局狀態

import Control.Monad.State 
import Control.Monad.IO.Class (liftIO) 

data Vars = Vars { 
var1 :: Int, 
var2 :: Float 
} deriving (Show) 

sample :: StateT Vars IO a 
sample = do 
     a <- change 
     liftIO $ print a 
     -- I want to call change again and apply more change to the state 


change :: StateT Vars IO a 
change = do 
     dd <- get 
     -- I don't know what to do next! 

main = do 
    runStateT sample (Vars 20 3) 
    evalStateT sample (Vars 20 3) 
+0

您能否指定該程序的所需輸出?運行'main'函數後你想觀察什麼? – Shersh

+0

@Shersh謝謝你的提示。我剛剛做完。 – 4xx

回答

2

讓我們試着從容易和小零件開始一步一步地解決您的問題。這是編程的重要技能,FP以很好的方式教給你這項技能。另外,使用State monad,尤其是在monad-transformers中有多種效果,可以幫助您更好地理解效果和理解事物。

  1. 您想在您的不可變數據類型中更新var1。這隻能通過創建新的對象來完成。因此,讓我們寫這樣的功能:

    plusFour :: Vars -> Vars 
    plusFour (Vars v1 v2) = Vars (v1 + 4) v2 
    

    有Haskell中存在的方式來寫這個功能要短得多,雖然不太理解的,但我們現在不關心這些事情。

  2. 現在要使用這個功能裏面State單子更新一成不變的狀態,並通過這個模擬的可變性。只有通過查看其類型簽名才能瞭解這個功能:change :: StateT Vars IO a?我們可以說這個函數有幾個作用:它可以訪問Vars狀態,它可以執行任意的IO動作。此函數也返回a類型的值。嗯,這最後一個很奇怪。什麼是a?這個函數應該返回什麼?在命令編程中,此功能的類型爲voidUnit。它只是的事情,它並沒有回報一切。只更新上下文。所以它的結果類型應該是()。它可以不同。例如,我們可能想在更改後返回新的Vars。但是這在編程中通常是不好的方法。它使這個功能更復雜。

  3. 當我們理解了函數應該具有的類型(試着總是從定義類型開始)之後,我們就可以實現它。我們想改變我們的狀態。有些功能與我們的上下文的有狀態部分一起運行。基本上,你有興趣在這一個:

    modify :: Monad m => (s -> s) -> StateT s m()

    modify函數採用函數更新狀態。運行此函數後,您可以觀察到狀態是根據傳遞函數修改的。現在change可以這樣寫:

    change :: StateT Vars IO() 
    change = modify plusFour 
    

    可以(只使用putget功能,這是很好的鍛鍊初學者,因此change)實施modify

  4. 現在讓我們從其他函數調用change函數。在這種情況下,調用意味着什麼?這意味着你執行一元行動change。這個動作改變你的上下文,你不關心它的結果,因爲它是()。但是如果在change之後運行get函數(它將整個狀態綁定到變量),您可以觀察到新的更改。如果只想打印更改的組件,例如var1,則可以使用gets功能。而且,再次,sample應具有什麼類型?它應該返回什麼?如果發送方你只關心結果狀態,然後再次,它應該是()這樣的:

    sample :: StateT Vars IO() 
    sample = do 
        change 
        v1 <- gets var1 
        liftIO $ print v1 
        change 
        v1' <- gets var1 
        liftIO $ print v1' -- this should be v1 + 4 
    

這應該添加你所發生的事情有所瞭解。 Monad變形金剛需要一些時間來適應他們,雖然它是一個強大的工具(不完美但非常有用)。

作爲一個便箋,我想補充一點,使用常見的Haskell設計模式,這些函數可以寫得更好。但是你現在不需要關心那些人,只是試着瞭解這裏發生了什麼。

+0

非常感謝您的確切答案! 「設計模式」是否指「鏡頭」? – 4xx

+0

@ 4xx«鏡頭»是一個庫,但它帶來了該模式的一部分。我的意思是,這種'change'函數應該是'change ::(MonadState s m,HasVar1 s)=> m()'。 – Shersh

+0

請問我可以參考一些資料來解釋爲什麼「變更」應該這樣寫? – 4xx