Writer monad 和无序

Writer monad and unsequence

我正在使用 Writer monad 来跟踪任意值(例如 Int)上的错误 ("collision") 标志。一旦设置了标志,它就是 "sticky" 并将其自身附加到作为标记的任何操作的结果产生的所有值。

有时碰撞标志与单个值相关联,有时我想与列表等复合结构相关联。当然,一旦为整个列表设置了冲突标志,就可以假设它是为单个元素设置的。所以对于 writer 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> 

鉴于此我有2个问题:

  1. 是否可以使用 monadic 和 comonadic 运算符定义 unsequence,而不是特定于 Writer
  2. 上面的extend函数有没有更优雅的实现,或许类似于this one

谢谢!

The ex1 produces correct value, while ex2 output is incorrectly not preserving collision flag:

unsequence(因此,ex2)不起作用,因为它会丢弃 Writer 日志。

unsequence :: (Comonad w, Monad m) => w [a] -> [m a]
unsequence = map return . extract

extract for your Comonad instance 给出计算结果,丢弃日志。 returnmempty 日志添加到裸结果中。既然如此,标志在 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 本质上并不是一个单子操作——sequence 中的 Monad 约束是不必要的限制。真正的交易是 sequenceA, which only requires an Applicative constraint on the inner functor. (If the outer Functor -- i.e. the one with the Traversable instance -- is like Writer w in that it always "holds" exactly one value, then you don't even need Applicative, but .)

Is it possible to define 'unsequence' using monadic and comonadic operators, not specific to 'Writer'

如上所述,您实际上并不想要 unsequence。有一个叫做Distributive的class确实提供了unsequence(在distribute的名字下);但是,具有 Distributive 个实例的事物与具有 Traversable 个实例的事物之间的重叠相对较少,并且在任何情况下它本质上都不涉及 comonads。

Is there is a more elegant implementatoin of extend function above, perhaps similar to this one?

您的 Comonad 实例很好(它确实遵循 the comonad laws), except that you don't actually need the Monoid constraint in it. The pair comonad is usually known as Env; see 来讨论它的作用。