Я пытаюсь следовать совету, данному в Объединить состояние с действиями ввода-вывода для создания 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 и напишу что-нибудь об этом, когда это наконец заработает в моей голове.