【发布时间】:2017-07-28 08:55:57
【问题描述】:
我正在使用Writer monad 来跟踪任意值(例如Int)上的错误(“冲突”)标志。一旦设置了标志,它就是“粘性”的,并且将自身附加到作为标记的任何操作的结果产生的所有值。
有时碰撞标志与单个值相关联,有时我想与组合结构(例如列表)相关联。当然,一旦为整个列表设置了冲突标志,假设它是为单个元素设置的也是有意义的。所以对于作家 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?
谢谢!
【问题讨论】:
-
您能否进一步激发对
unsequence的需求?为什么在结果列表上进行绑定然后进行模式匹配(正如您所观察到的,它将正确地将您的脏位传播到使用您在模式匹配中绑定的列表元素完成的任何计算)足够好? -
基本上,这是为了方便。我想在单个值和列表上混合使用提升和未提升的功能。例如,某些函数可以生成 [Int] 类型的元素列表,然后我想使用二进制函数
f: CInt->Cint来映射此列表。 -
unsequence如何让这更方便?对于action :: M [Int]和f :: Int -> Int,两个表达式分别是map f <$> action和mapM (f <$>) (unsequence action);在这两者中,我更喜欢前者,甚至在我查看unsequence是否可以实现之前。或者如果你的意思是f' :: Int -> M Int,那么这两个是action >>= mapM f'vsmapM (>>= f') (unsequence action),我更喜欢不使用unsequence的那个。 -
我并不是说
unsequence是没有根据的。我只是鼓励您尝试更清晰地描述为什么需要它 - 您的目标越明确,人们就如何实现您的目标提供更好的建议!
标签: haskell monads comonad writer-monad