How do I add lists or ListTs to this monad transformer?
I have a game record that represents the current state of the game.
data Game = Game { score :: Int, turn :: Int }
I want to be able to create a bunch of functions to change the state of the game and also use a random number generator and also keep a log of what happened in order to go from one state to another. Therefore, I created an entry GameState
containing additional information.
type History = [String]
data GameState = GameState Game StdGen History
Now I want to create a datatype for functions that will act on this GameState
. These will be modeled after game updates as well as rolling dice and logging. So I created a monad transformer for all the effects I want.
type Effect = WriterT History (RandT StdGen (State Game))
Writing a function to run Effect
in a given is GameState
pretty straightforward.
runEffect :: GameState -> Effect () -> GameState
runEffect (GameState game stdGen history) effect =
let ((((), newHist), newGen), newGame) =
runState (runRandT (runWriterT effect) stdGen) game
in GameState newGame newGen newHist
Perfect. Now I want to create one more thing. Some Effects
may have several different results GameStates
. Therefore, mine runEffect
must really return [GameState]
. I need to add ListT
to this monad transformer maybe. And then all mine Effects
will have the ability to produce more than one result, if need be. But also, if they are just a one-to-one mapping, then they can act the same way too.
I tried to make the following changes:
type Effect2 = ListT (WriterT [String] (RandT StdGen (State Game)))
runEffect2 :: GameState -> Effect2 a -> [GameState]
runEffect2 (GameState game stdGen history) effect =
let l = runListT effect
result = map (\e->runState (runRandT (runWriterT e) stdGen) game) l
in map (\((((), newHist), newGen), newGame)->
GameState newGame newGen newHist)
result
What I am trying to do is add ListT
to the transformer outside Writer
and Random
and State
because I want different computation branches to have different histories and independent states and random generators. But it doesn't work. I am getting the following type error.
Prelude Ξ»: :reload [1 of 1] Compiling Main ( redux.hs, interpreted )
redux.hs:31:73: error:
β’ Couldn't match expected type β[WriterT
w
(RandT StdGen (StateT Game Data.Functor.Identity.Identity))
a1]β
with actual type βWriterT [String] (RandT StdGen (State Game)) [a]β
β’ In the second argument of βmapβ, namely βlβ
In the expression:
map (\ e -> runState (runRandT (runWriterT e) stdGen) game) l
In an equation for βresultβ:
result
= map (\ e -> runState (runRandT (runWriterT e) stdGen) game) l
β’ Relevant bindings include
result :: [(((a1, w), StdGen), Game)] (bound at redux.hs:31:7)
l :: WriterT [String] (RandT StdGen (State Game)) [a]
(bound at redux.hs:30:7)
effect :: Effect2 a (bound at redux.hs:29:44)
runEffect2 :: GameState -> Effect2 a -> [GameState]
(bound at redux.hs:29:1)
Failed, modules loaded: none.
Does anyone know what I am doing wrong? I really want to be able to expand one GameState
by several GameStates
. Each of them has independent StdGen
and History
for this branch. I did this by putting everything in a post Game
and just using non-monodic functions for the effects. It works, and it's pretty straight forward. However, the composition of these functions is really annoying because they act like a state and I have to deal with it myself. This is what monads are great, so I decided to reuse this here would be wise. Unfortunately, this aspect of the list really confused me.
source to share
First, the immediate cause of the error is that the type runListT
...
GHCi> :t runListT
runListT :: ListT m a -> m [a]
... but you use it as if it produced [m a]
, not m [a]
. In other words, it shouldn't be map
in the definition result
.
Second, in the monadic stack, the inner monads rule over the outer ones. For example, packing, StateT
c ListT
, results in a state computation based on the state of the garden, which leads to several results. We can see that by specializing in the type runListT
:
GHCi> :set -XTypeApplications
GHCi> :t runListT @(StateT _ _)
runListT @(StateT _ _) :: ListT (StateT t t1) a -> StateT t t1 [a]
Wrapping ListT
with help StateT
, on the other hand, gives us a calculation that produces multiple states as well as the results:
GHCi> :t runStateT @_ @(ListT _)
runStateT @_ @(ListT _)
:: StateT t (ListT t1) a -> t -> ListT t1 (a, t)
Thus, you want to swap the transformers in your stack. If you want to have multiple effects for everything as you described, and you don't need it IO
as your base monad, you don't need it at all ListT
- just put the []
stack at the bottom.
Third, avoid transformers in a tangent note ListT
. It is known to be illegal and is deprecated in the latest version of transformers . An easy replacement for this is provided by list-t package . (If at some point further down the road you can use the streaming channel library, you may also find your own versionListT
helpful.)
source to share