Using the StateT
monad transformer, I can create the type StateT s [] a
, which is isomorphic to s -> [(a, s)]
. Now I would prefer to use the STT
monad transformer instead, as I would like to have multiple mutable variables of different types, and would like to be able to instantiate them at will, depending on the results of earlier computations.
However, the linked documentation for STT
mentions explicitly:
This monad transformer should not be used with monads that can contain multiple answers, like the list monad. The reason is that the state token will be duplicated across the different answers and this causes Bad Things to happen (such as loss of referential transparency). Safe monads include the monads State, Reader, Writer, Maybe and combinations of their corresponding monad transformers.
So what are my options?
To be entirely clear:
EDIT:
(Edit to edit: the following counterexample is invalid, as ListT
should not be applied to the non-commutative monads ST
and State
.)
I've come to realize that an STT
monad transformer that behaves along the lines of StateT
is inherently unsafe. With it, we could build a type STT sloc (ListT (ST sglob)) a
. Here, sglob
is the name of the global state, while sloc
is the name of the local state.*
Now we can use the global state to exchange local state references between threads, thus potentially obtaining references to uninitialized variables.
*For comparison, the corresponding StateT
construction is StateT sloc (ListT (State sglob)) a
, which is isomorphic to sloc -> sglob -> ([(a, sloc)], sglob)
.
You won't get around StateT
, because for this nondeterminism stuff the compiler needs to know always which “variables” need to be branched out. This is not possible when the variables could be anywhere lurking as STRef
s.
To still get “multiple variables of different types”, you'll need to pack them in a suitable record and use that as the single actual state variable. Seems awkward to deal with such a state object? Well, it's not that bad when using lenses to access the “individual variables”.
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Data.Monoid
import Control.Monad.Trans.State
import Control.Monad.ListT
import Control.Monad.Trans.Class
import Control.Monad.IO.Class
data Stobjs = Stobjs {
_x :: Int
, _y :: String
}
makeLenses ''Stobjs
main = runListT . (`runStateT`Stobjs 10 "") $ do
δx <- lift $ return 1 <> return 2 <> return 3
xnow <- x <+= δx
y .= show xnow
if xnow > 11 then liftIO . putStrLn =<< use y
else lift mempty
(outputs 12
).
“Able to instantiate them at will” is a bit more tricky, because adding variables is only possible through changing the state object, which means you'll not really be in the same monad anymore. Lens has the notion of zooming which could be used – splitting up the state object into “scopes” and using the computations where only some of the variables are defined as zoomed in onto that scope.
To make this really convenient you'd need records that can be extended at will. I really liked Nikita Volkovs record
library approach, this doesn't seem to have been advanced any further lately. Vinyl also goes in that direction, but I haven't looked into it much.
In the future, we will have the OverloadedRecordFields
extension that will help with this kind of stuff.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With