Сводка: разные заказы на стопку дают различную бизнес-логику
То есть различные порядки монадных преобразователей стека влияют не только на порядки оценки, но и на функциональность программ.
При демонстрации влияния заказов люди обычно используют самые простые преобразователи, такие как ReaderT
, WriterT
, StateT
, MaybeT
, ExceptT
. Разные их порядки не дают принципиально разной бизнес-логики, поэтому трудно ясно понять влияние. Кроме того, некоторые их подмножества являются коммутативными, т. Е. Функциональных различий нет.
В демонстрационных целях я предлагаю использовать StateT
и ListT
, которые показывают резкое различие между порядками трансформаторов в стеках монад.
Фон: StateT
и ListT
StateT
: State
Монада хорошо объяснена в Для нескольких монад Подробнее . StateT
просто дает вам немного больше энергии - используя монадические операции его базового m
. Достаточно, если вы знаете evalStateT
, put
, get
и modify
, что объясняется во многих State
руководствах по монадам.
ListT
: List
, a.k.a, []
, является монадой (объяснено в Горсть монад ). ListT m a
(в пакете list-t
) дает вам что-то похожее на [a]
плюс все монадические операции базовой монады m
. Сложная часть - выполнение ListT
(что-то сравнимое с evalStateT
): существует множество способов выполнения. Подумайте о различных результатах, которые вам небезразличны при использовании evalStateT
, runStateT
и execState
, в контексте монады List
много потенциальных потребителей, таких как , просто выберите их , то есть traverse_
, сложите их , т. Е. fold
и более.
Эксперимент: понять влияние порядка монадического трансформатора
Мы создадим простой двухслойный стек монадных трансформеров, используя StateT
и ListT
поверх IO
, чтобы выполнить некоторые функции для демонстрации.
Описание задачи
Суммирование чисел в потоке
Поток будет абстрагирован как список Integer
с, поэтому приходит наш ListT
. Чтобы суммировать их, нам нужно сохранять состояние суммы при обработке каждого элемента в потоке, где наш StateT
приходит.
Два стека
У нас есть простое состояние: Int
, чтобы сохранить сумму
ListT (StateT Int IO) a
StateT Int (ListT IO) a
Полная программа
#!/usr/bin/env stack
-- stack script --resolver lts-11.14 --package list-t --package transformers
import ListT (ListT, traverse_, fromFoldable)
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State (StateT, evalStateT, get, modify)
main :: IO()
main = putStrLn "#### Task: summing up numbers in a stream"
>> putStrLn "#### stateful (StateT) stream (ListT) processing"
>> putStrLn "#### StateT at the base: expected result"
>> ltst
>> putStrLn "#### ListT at the base: broken states"
>> stlt
-- (ListT (StateT IO)) stack
ltst :: IO ()
ltst = evalStateT (traverse_ (\_ -> return ()) ltstOps) 10
ltstOps :: ListT (StateT Int IO) ()
ltstOps = genLTST >>= processLTST >>= printLTST
genLTST :: ListT (StateT Int IO) Int
genLTST = fromFoldable [6,7,8]
processLTST :: Int -> ListT (StateT Int IO) Int
processLTST x = do
liftIO $ putStrLn "process iteration LTST"
lift $ modify (+x)
lift get
printLTST :: Int -> ListT (StateT Int IO) ()
printLTST = liftIO . print
-- (StateT (ListT IO)) stack
stlt :: IO ()
stlt = traverse_ (\_ -> return ())
$ evalStateT (genSTLT >>= processSTLT >>= printSTLT) 10
genSTLT :: StateT Int (ListT IO) Int
genSTLT = lift $ fromFoldable [6,7,8]
processSTLT :: Int -> StateT Int (ListT IO) Int
processSTLT x = do
liftIO $ putStrLn "process iteration STLT"
modify (+x)
get
printSTLT :: Int -> StateT Int (ListT IO) ()
printSTLT = liftIO . print
Результаты и объяснение
$ ./order.hs
#### Task: summing up numbers in a stream
#### stateful (StateT) stream (ListT) processing
#### StateT at the base: expected result
process iteration LTST
16
process iteration LTST
23
process iteration LTST
31
#### ListT at the base: broken states
process iteration STLT
16
process iteration STLT
17
process iteration STLT
18
Первый стек ListT (StateT Int IO) a
дает правильный результат, поскольку StateT
вычисляется после ListT
. При оценке StateT
исполняющая система уже оценила все операции ListT
- заполняя стек потоком [6,7,8]
, проходя через них traverse_
. Слово оцененный здесь означает, что эффекты ListT
исчезли, а ListT
теперь прозрачен для StateT
.
Второй стек StateT Int (ListT IO) a
не имеет правильного результата, поскольку StateT
слишком короток. На каждой итерации ListT
оценки, a.k.a., traverse_
, состояние создается, оценивается и исчезает. StateT
в этой структуре стека не достигает своей цели сохранения состояний между операциями элемента списка / потока.