0

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

有时碰撞标志与单个值相关联,有时我想与组合结构(例如列表)相关联。当然,一旦为整个列表设置了冲突标志,假设它是为单个元素设置的也是有意义的。因此,对于 writer monad m,我需要以下两个操作:

sequence :: [m a] -> m [a]
unsequence :: m [a] -> [m a]

第一个在 Prelude 中定义,而第二个必须定义。是关于如何使用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. 是否可以unsequence使用不特定于 monadic 和 comonadic 运算符来定义Writer
  2. 上面的函数是否有更优雅的实现extend,可能类似于这个

谢谢!

4

1 回答 1

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是,它本质上不是一元操作——in 的Monad约束sequence是不必要的限制。真正的交易是sequenceA,它只需要Applicative对内部函子进行约束。(如果外部Functor——即带有Traversable实例的那个——就像Writer w它总是“持有”一个值,那么你甚至不需要Applicative,但那是另一回事了。)

是否可以使用 monadic 和 comonadic 运算符定义“unsequence”,而不是特定于“Writer”

如上所述,您实际上并不想要unsequence. 有一个名为的类Distributive确实提供unsequence(在名称下distribute);Distributive但是,带有实例的事物和带有实例的事物之间的重叠相对较少Traversable,并且无论如何它本质上并不涉及共子。

上面有没有更优雅的扩展函数实现,可能类似于这个?

您的Comonad实例很好(它确实遵循comonad law),但您实际上并不需要其中的Monoid约束。对comonad通常被称为Env; 请参阅此答案以讨论它的作用。

于 2017-03-08T01:45:24.970 回答