我正在使用Writer
monad来跟踪任意值(例如Int
)上的错误(" collision")标记。一旦设置了标志,它就是"粘性"并将自身附加到由于标记的任何操作而产生的所有值。
有时,碰撞标志与各个值相关联,有时我想与列表等复合结构相关联。当然,一旦为整个列表设置了碰撞标志,假设它是为单个元素设置也是有意义的。因此,对于作家monad m
,我需要以下两个操作:
sequence :: [m a] -> m [a]
unsequence :: m [a] -> [m a]
第一个是在Prelude中定义的,而第二个是必须定义的。 Here很好地讨论了如何使用 comonads 定义它。本机comonad实现不保留状态。这是一个例子:
{-# LANGUAGE FlexibleInstances #-}
module Foo where
import Control.Monad.Writer
import Control.Comonad
unsequence :: (Comonad w, Monad m) => w [a] -> [m a]
unsequence = map return . extract
instance Monoid Bool where
mempty = False
mappend = (||)
type CM = Writer Bool
type CInt = CM Int
instance (Monoid w) => Comonad (Writer w) where
extract x = fst $ runWriter x
extend f wa = do { tell $ execWriter wa ; return (f wa)}
mkCollision :: t -> Writer Bool t
mkCollision x = do (tell True) ; return x
unsequence1 :: CM [Int] -> [CInt]
unsequence1 a = let (l,f) = runWriter a in
map (\x -> do { tell f ; return x}) l
el = mkCollision [1,2,3]
ex2:: [CInt]
ex2 = unsequence el
ex1 = unsequence1 el
ex1
生成正确的值,而ex2
输出错误地不保留碰撞标记:
*Foo> ex1
[WriterT (Identity (1,True)),WriterT (Identity (2,True)),WriterT (Identity (3,True))]
*Foo> ex2
[WriterT (Identity (1,False)),WriterT (Identity (2,False)),WriterT (Identity (3,False))]
*Foo>
鉴于此,我有两个问题:
unsequence
,而不是Writer
特有的?extend
函数是否有更优雅的实现,可能类似于this one?谢谢!
答案 0 :(得分:4)
ex1
生成正确的值,而ex2
输出错误地不保留碰撞标记:
unsequence
(因此,ex2
)无法正常工作,因为它会丢弃Writer
日志。
unsequence :: (Comonad w, Monad m) => w [a] -> [m a]
unsequence = map return . extract
extract
实例的 Comonad
给出计算结果,丢弃日志。 return
将mempty
日志添加到裸结果中。既然如此,标志将在ex2
中清除。
unsequence1
可以满足您的需求。这显然与Comonad
没有任何关系(你的定义并没有使用它的方法);相反,unsequence1
有效,因为......它实际上是sequence
!在引擎盖下,Writer
只是一对结果和一个(幺半)对数。如果您再次考虑unsequence1
,请注意(模数不相关的详细信息)它与sequence
对的基本相同 - 它对另一个仿函数中的值进行注释与日志:
GHCi> sequence (3, [1..10])
[(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),(3,7),(3,8),(3,9),(3,10)]
事实上,Writer
已经有Traversable
个实例,所以你甚至不需要定义它:
GHCi> import Control.Monad.Writer
GHCi> import Data.Monoid -- 'Any' is your 'Bool' monoid.
GHCi> el = tell (Any True) >> return [1,2,3] :: Writer Any [Int]
GHCi> sequence el
[WriterT (Identity (1,Any {getAny = True})),WriterT (Identity (2,Any {getAny = True})),WriterT (Identity (3,Any {getAny = True}))]
值得一提的是,sequence
不是一个基本上是monadic的操作 - Monad
中的sequence
约束是不必要的限制。真正的交易是sequenceA
,它只需要对内部仿函数进行Applicative
约束。 (如果外部Functor
- 即具有Traversable
实例的那个 - 就像Writer w
一样,它总是"保持"恰好一个值,那么你就不要甚至需要Applicative
,但that's another story。)
是否可以定义' unsequence'使用monadic和comonadic运算符,不是特定于' Writer'
如上所述,您实际上并不想要unsequence
。有一个名为Distributive
的班级确实提供unsequence
(名称为distribute
);但是,Distributive
个实例与Traversable
个实例之间的重叠程度相对较小,而且无论如何它实际上并不涉及comonads。
上面的扩展函数是否有更优雅的实现,或许与此类似?
您的Comonad
个实例很好(它跟在the comonad laws之后),但您实际上并不需要Monoid
约束。这对comonad通常称为Env
;请参阅this answer以了解它的作用。