I am trying to replace StateT
with AccumT
in my program but I can't figure out exactly how it works. Specifically, I don't understand why runAccumT
and its derivatives seem to ignore their second argument. For example, I was expecting
execAccumT (add 7) 8 :: IO Int
to print 15
, but it prints 7
. What am I missing here?
Updated with a workaround.
First, note that Accum
uses a general Monoid
, not just a sum, so even though your code happens to work because it involves only one monadic add
, anything that actually involved multiple accumulator operations would fail to type check:
> execAccumT (add 7 >> add 8) 0 :: IO Int
<interactive>:39:19-20: error:
• No instance for (Monoid Int) arising from a use of ‘>>’
...
Instead, as in @DanielWagner's answer, you need to use the Sum
monoid:
> execAccumT (add 7 >> add 8) 0 :: IO (Sum Int)
Sum {getSum = 15}
You can continue to write add 7
instead of add (Sum 7)
because Sum Int
has a Num
instance that automatically converts numeric literatals to sums, and allows simple arithmetic operations on them, but if you wanted to add x
where x :: Int
, you'd need to write add (Sum x)
explicitly.
Second, there does appear to be a bug in the handling of the initial value of the accumulator, what the documentation calls its "initial history". If you write:
> execAccumT (add 7 >> add 8) 999 :: IO (Sum Int)
it appears that the initial history is ignored, but if you use runAccumT
to simultaneously inspect both what look
thinks is the final history, and the final history returned by runAccumT
, you can see a discrepancy:
λ> runAccumT (add 7 >> add 8 >> look) 999 :: IO (Sum Int, Sum Int)
(Sum {getSum = 1014},Sum {getSum = 15})
That is, look
's opinion of the final history is that it includes the initial history, but the final history returned by runAccumT
doesn't include the initial history.
For now, it's safe to use Accum
with an "mempty
" initial history, i.e., whatever is the appropriate zero value for the Monoid
, and use an initial add
operation if you want to start with something other than mempty
.
If you want to use a non-mempty
history, I suggest importing with a few functions hidden and use these replacements:
import Data.Functor.Identity
import Control.Monad.Trans.Accum hiding (runAccum, execAccum, runAccumT, execAccumT)
runAccumT :: (Functor m, Monoid w) => AccumT w m a -> w -> m (a,w)
runAccumT (AccumT f) w = (\(a, w') -> (a, w <> w')) <$> f w
execAccumT :: (Monad m, Monoid w) => AccumT w m a -> w -> m w
execAccumT m = fmap snd . runAccumT m
runAccum :: (Monoid w) => Accum w a -> w -> (a, w)
runAccum m = runIdentity . runAccumT m
execAccum :: (Monoid w) => Accum w a -> w -> w
execAccum m = snd . runAccum m
These should work fine:
λ> runAccumT (add 7 >> add 8 >> look) 999 :: IO (Sum Int, Sum Int)
(Sum {getSum = 1014},Sum {getSum = 1014})