作家monad和unsequence

时间:2017-03-07 23:32:35

标签: haskell monads comonad writer-monad

我正在使用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> 

鉴于此,我有两个问题:

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

1 个答案:

答案 0 :(得分:4)

  

ex1生成正确的值,而ex2输出错误地不保留碰撞标记:

unsequence(因此,ex2)无法正常工作,因为它会丢弃Writer日志。

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

Comonad给出计算结果,丢弃日志。 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不是一个基本上是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以了解它的作用。