我试图在我的程序中用AccumT替换StateT,但我不能确切地弄清楚它是如何工作的。具体来说,我不明白为什么runAccumT及其衍生物似乎忽略了它们的第二个参数。比如说,我本来以为
AccumT
StateT
runAccumT
execAccumT (add 7) 8 :: IO Int
字符串打印15,但它打印7。我错过了什么?
15
7
zazmityj1#
首先,请注意Accum使用的是一个通用的Monoid,而不仅仅是一个sum,所以即使你的代码碰巧工作,因为它只涉及一个一元add,任何实际涉及多个累加器操作的代码都将无法进行类型检查:
Accum
Monoid
add
> execAccumT (add 7 >> add 8) 0 :: IO Int<interactive>:39:19-20: error: • No instance for (Monoid Int) arising from a use of ‘>>’ ...
> execAccumT (add 7 >> add 8) 0 :: IO Int
<interactive>:39:19-20: error:
• No instance for (Monoid Int) arising from a use of ‘>>’
...
字符串相反,正如@DanielWagner的回答,你需要使用Sum幺半群:
Sum
> execAccumT (add 7 >> add 8) 0 :: IO (Sum Int)Sum {getSum = 15}
> execAccumT (add 7 >> add 8) 0 :: IO (Sum Int)
Sum {getSum = 15}
型你可以继续写add 7而不是add (Sum 7),因为Sum Int有一个Num示例,它可以自动将数字转换为和,并允许对它们进行简单的算术运算,但是如果你想在x :: Int中写add x,你需要显式地写add (Sum x)。第二,在处理累加器的初始值时确实存在一个bug,文档称之为“初始历史”。如果你写:
add 7
add (Sum 7)
Sum Int
Num
x :: Int
add x
add (Sum x)
> execAccumT (add 7 >> add 8) 999 :: IO (Sum Int)
型看起来初始历史记录被忽略了,但是如果使用runAccumT同时检查look认为是最终历史记录的内容和runAccumT返回的最终历史记录,您可以看到一个差异:
look
λ> runAccumT (add 7 >> add 8 >> look) 999 :: IO (Sum Int, Sum Int)(Sum {getSum = 1014},Sum {getSum = 15})
λ> runAccumT (add 7 >> add 8 >> look) 999 :: IO (Sum Int, Sum Int)
(Sum {getSum = 1014},Sum {getSum = 15})
型也就是说,look对最终历史的看法是它包括初始历史,但runAccumT返回的最终历史不包括初始历史。目前,使用带有“mempty”初始历史的Accum是安全的,即Monoid的适当零值,如果您想从mempty以外的其他值开始,则使用初始add操作。如果你想使用一个非mempty的历史记录,我建议导入一些隐藏的函数,并使用这些替换:
mempty
import Data.Functor.Identityimport 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 wexecAccumT :: (Monad m, Monoid w) => AccumT w m a -> w -> m wexecAccumT m = fmap snd . runAccumT mrunAccum :: (Monoid w) => Accum w a -> w -> (a, w)runAccum m = runIdentity . runAccumT mexecAccum :: (Monoid w) => Accum w a -> w -> wexecAccum m = snd . runAccum m
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
型这些应该可以正常工作:
λ> runAccumT (add 7 >> add 8 >> look) 999 :: IO (Sum Int, Sum Int)(Sum {getSum = 1014},Sum {getSum = 1014})
(Sum {getSum = 1014},Sum {getSum = 1014})
型
ds97pgxw2#
只有三个基本操作检查传入的累积值:accum、look和looks。因此,如果您希望参数为runAccumT,则必须使用其中一个作为您的第一个操作来显式地请求它。
accum
looks
> execAccumT (look >>= add) 7 :: IO (Sum Int)Sum {getSum = 7}
> execAccumT (look >>= add) 7 :: IO (Sum Int)
Sum {getSum = 7}
字符串确实有点奇怪
2条答案
按热度按时间zazmityj1#
首先,请注意
Accum
使用的是一个通用的Monoid
,而不仅仅是一个sum,所以即使你的代码碰巧工作,因为它只涉及一个一元add
,任何实际涉及多个累加器操作的代码都将无法进行类型检查:字符串
相反,正如@DanielWagner的回答,你需要使用
Sum
幺半群:型
你可以继续写
add 7
而不是add (Sum 7)
,因为Sum Int
有一个Num
示例,它可以自动将数字转换为和,并允许对它们进行简单的算术运算,但是如果你想在x :: Int
中写add x
,你需要显式地写add (Sum x)
。第二,在处理累加器的初始值时确实存在一个bug,文档称之为“初始历史”。如果你写:
型
看起来初始历史记录被忽略了,但是如果使用
runAccumT
同时检查look
认为是最终历史记录的内容和runAccumT
返回的最终历史记录,您可以看到一个差异:型
也就是说,
look
对最终历史的看法是它包括初始历史,但runAccumT
返回的最终历史不包括初始历史。目前,使用带有“
mempty
”初始历史的Accum
是安全的,即Monoid
的适当零值,如果您想从mempty
以外的其他值开始,则使用初始add
操作。如果你想使用一个非
mempty
的历史记录,我建议导入一些隐藏的函数,并使用这些替换:型
这些应该可以正常工作:
型
ds97pgxw2#
只有三个基本操作检查传入的累积值:
accum
、look
和looks
。因此,如果您希望参数为runAccumT
,则必须使用其中一个作为您的第一个操作来显式地请求它。字符串
确实有点奇怪