Как на самом деле выполнить монаду StateT вместе с IO?

Я пытаюсь следовать совету, данному в Объединить состояние с действиями ввода-вывода для создания AppState вместе с монадой ввода-вывода. Вот что я получил:

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иmyMainи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

Я думал, что цель монадного преобразователя заключается в том, чтобы я мог выполнять свои операции с монадой State в функции (, как показано выше ), и поднимать операции ввода-вывода в эту функцию. Как правильно настроить все это, чтобы я мог удалить один из уровней косвенности?


В дополнение к решению Даниэля (, которое я пометил как решение ), я также нашел несколько вариантов, которые также могут пролить некоторый свет на ситуацию. Во-первых, окончательная реализация 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 отличается от базовой монады State, и я кодировал как sumST, так и append для монады State. В некотором смысле их тоже нужно было поднять в монаду StateT,хотя правильный способ думать об in состоит в том, что они должны запускаться в монаде (, следовательно,runState script new).

Я не уверен, что полностью понял это, но я поработаю с ним некоторое время, прочитаю код MonadState и напишу что-нибудь об этом, когда это наконец заработает в моей голове.

5
задан Community 23 May 2017 в 11:54
поделиться