概要:スタックの注文が異なれば、ビジネスロジックも異なります。
つまり、スタックのモナド変換子の順序が異なると、評価の順序だけでなく、プログラムの機能にも影響します。
注文の影響を示すとき、人々は通常、、、、、、などの最も単純なトランスフォーマーをReaderT
使用します。順序が異なってもビジネスロジックが劇的に異なるわけではないため、影響を明確に理解することは困難です。さらに、それらの一部のサブセットは可換です。つまり、機能の違いはありません。WriterT
StateT
MaybeT
ExceptT
デモンストレーションの目的で、モナドスタックの変圧器の順序の劇的な違いを明らかにするStateT
andを使用することをお勧めします。ListT
背景:StateT
およびListT
StateT
:State
モナドは、For a FewMonadsMoreで詳しく説明されています。StateT
基になるのモナディック操作を使用して、もう少しパワーを与えますm
。多くのモナドチュートリアルで説明されている、、、、およびを知っevalStateT
ていれば十分です。put
get
modify
State
ListT
:List
、別名、、はモナドです( A Fistful of Monads[]
で説明されています)。(パッケージ内)は、基になるモナドのすべてのモナド操作に似たものを提供します。トリッキーな部分は(に匹敵するもの)の実行です:実行の方法はたくさんあります。、、、を使用するときに気になるさまざまな結果について考えてみてください。モナドのコンテキストには、それらを調べる、つまり、折りたたむ、など、多くの潜在的な消費者がいます。ListT m a
list-t
[a]
m
ListT
evalStateT
evalStateT
runStateT
execState
List
traverse_
fold
実験:モナド変換子の順序の影響を理解する
デモンストレーション用のいくつかの機能を実行するために、を使用StateT
して、そのListT
上に単純な2層のモナドトランスフォーマースタックを構築します。IO
タスクの説明
ストリーム内の数値を合計する
Integer
ストリームはsのリストとして抽象化されるので、私たちListT
が入ります。それらを合計するには、私たちが来るストリーム内の各アイテムを処理している間、合計の状態を維持する必要がありますStateT
。
2つのスタック
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
2番目のスタックは寿命が短すぎるStateT Int (ListT IO) a
ため、正しい結果が得られません。StateT
評価のすべての反復でListT
、別名、、traverse_
状態が作成され、評価され、消滅します。このStateT
スタック構造のは、リスト/ストリームアイテム操作間で状態を維持するという目的を達成していません。