我如何实际执行State monad和IO?

时间:2012-07-06 13:16:03

标签: haskell monads monad-transformers

我正在尝试按照Combine state with IO actions中给出的建议来建立一个AppState以及一个IO monad。我得到的是:

module Main where

import Control.Monad.State
import Control.Monad.Trans

data ST = ST [Integer] deriving (Show)
type AppState = StateT ST IO

new = ST []

append :: Integer -> State ST ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))

sumST :: State ST Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)

script = do
    append 5
    append 10
    append 15
    sumST

myMain :: AppState ()
myMain = do
    liftIO $ putStrLn "myMain start"
    let (res, st) = runState script new
    liftIO $ putStrLn $ show res
    liftIO $ putStrLn "myMain stop"

main = runStateT myMain (ST [15])

这部分内容我没有得到。我有scriptmyMain main,这让我很烦恼。令我困扰的是,我必须在runState内执行myMain,并且我必须在我的主函数中将初始状态提供给runStateT。我想要直接在myMain函数中使用我的“脚本”,因为myMain的整个点是能够直接在myMain中运行append和sum,并且紧跟打印操作。我想我应该能够做到这一点,而不是:

myMain :: AppState ()
myMain = do
    liftIO $ putStrLn "myMain start"
    append 5
    append 10
    append 15
    r <- sumST
    liftIO $ putStrLn $ show res
    liftIO $ putStrLn "myMain stop"

main = runState myMain

我原本以为monad变换器的重点在于我可以在函数中执行状态monad操作(如上所述)并将IO操作提升到该函数中。设置所有这些的正确方法是什么,以便我可以删除其中一个间接层?


除了Daniel的解决方案(我已经标记了解决方案)之外,我还发现了一些可能会对情况有所了解的变化。首先,myMain和main的最终实现:

myMain :: AppState ()
myMain = do
    liftIO $ putStrLn "myMain start"
    append 5
    append 10
    append 15
    res <- sumST
    liftIO $ putStrLn $ show res
    liftIO $ putStrLn "myMain stop"

main = runStateT myMain new

现在,除了丹尼尔之外,还有append和sumST的各种实现:

append :: Integer -> AppState ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))

sumST :: AppState Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)

和(请注意,只有类型声明会更改;实际上您可以完全省略类型声明!)

append :: MonadState ST m => Integer -> m ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))

sumST :: MonadState ST m => m Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)

我想到AppState / StateT monad 和基本的State monad相同,而且我正在编码sumST并为State monad添加。从某种意义上说,他们也必须被提升到StateT monad,尽管正确的思考方式是他们必须在monad中运行(因此,runState script new)。

我不确定我是否完全得到它,但我会使用它一段时间,阅读MonadState代码,并在它最终在我脑海中起作用时写下一些内容。

1 个答案:

答案 0 :(得分:10)

问题是你的appendsumST函数太单态了!您应该使用更多态stateget函数,而不是直接使用put函数,这样您就可以为它们提供更激动人心的类型

append :: MonadState ST m => Integer -> m ()
append v = do
    ST lst <- get
    put (ST (lst ++ [v]))

sumST :: MonadState ST m => m Integer
sumST = do
    ST lst <- get
    return (sum lst)

然后你可以准确写出你提出的myMain(尽管你仍然需要在main中给出一个初始状态。)

作为一种风格的东西,我建议不要定义一个新的ST类型:有许多函数可以使用列表进行处理,并且通过强加ST构造函数使它们无法使用你和名单之间可能很烦人!如果您使用[Integer]作为州类型,则可以进行如下定义:

prepend :: MonadState [Integer] m => Integer -> m ()
prepend = modify . (:)

sumST :: MonadState [Integer] m => m Integer
sumST = gets sum

看起来很漂亮,不是吗? =)