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

Sav*_*nel 5 monads haskell monad-transformers

我试图遵循Combine状态中给出的建议和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])
Run Code Online (Sandbox Code Playgroud)

有一部分我没有得到.它困扰了我很大的,我有scriptmyMain 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
Run Code Online (Sandbox Code Playgroud)

我曾经认为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
Run Code Online (Sandbox Code Playgroud)

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

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

sumST :: AppState Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

它发生,我认为届时AppState/StateT单子是一样的国家基本单子,我是既编码和sumST追加为国家单子.从某种意义上说,他们也必须被提升到StateT monad,尽管正确的思考方式是他们必须在monad中运行(因此runState script new).

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

Dan*_*ner 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)
Run Code Online (Sandbox Code Playgroud)

然后你可以准确写出myMain你提出的建议(尽管你仍然必须给出一个初始状态main).

作为一个风格的东西,我建议不要定义一个新ST类型:有许多函数可以使用列表进行方便的操作,并且通过ST在您和列表之间强加构造函数使它们无法使用它们会很烦人!如果您使用的[Integer]是状态类型,则可以进行如下定义:

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

sumST :: MonadState [Integer] m => m Integer
sumST = gets sum
Run Code Online (Sandbox Code Playgroud)

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