5

我正在尝试遵循将状态与 IO 操作结合起来构建 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])

这其中的某些部分我没有得到。script我有andmyMain 让我很困扰main。它还困扰着我,我必须在runState内部执行,并且我必须在我的主函数中myMain输入初始状态。runStateT我想要我的“脚本”,可以这么说,直接在 myMain 函数中,因为 myMain 的全部意义在于能够直接在 myMain 和打印操作旁边运行追加和求和。我想我应该能够做到这一点,而不是:

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 转换器的目的是让我可以在函数中执行我的 State 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 的各种实现,除了 Daniel 的:

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 不同,我正在为 State monad 编写 sumST 和 append 代码。从某种意义上说,它们也必须被提升到 StateT monad 中,尽管正确的思考方式是它们必须在 monad 中运行(因此,runState script new)。

我不确定我是否完全理解它,但我会使用它一段时间,阅读 MonadState 代码,并在它最终在我脑海中运行时写一些关于它的东西。

4

1 回答 1

10

问题是你让你的appendsumST函数太单态了!与其直接使用state函数,不如使用更多的多态getput函数,这样才能赋予它们更精彩的类型

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

看起来很不错,不是吗?=)

于 2012-07-06T13:27:28.503 回答