【问题标题】:Writer monad and unsequence作家单子和无序
【发布时间】: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> 

鉴于此,我有两个问题:

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

谢谢!

【问题讨论】:

  • 您能否进一步激发对unsequence 的需求?为什么在结果列表上进行绑定然后进行模式匹配(正如您所观察到的,它将正确地将您的脏位传播到使用您在模式匹配中绑定的列表元素完成的任何计算)足够好?
  • 基本上,这是为了方便。我想在单个值和列表上混合使用提升和未提升的功能。例如,某些函数可以生成 [Int] 类型的元素列表,然后我想使用二进制函数 f: CInt->Cint 来映射此列表。
  • unsequence 如何让这更方便?对于action :: M [Int]f :: Int -> Int,两个表达式分别是map f <$> actionmapM (f <$>) (unsequence action);在这两者中,我更喜欢前者,甚至在我查看unsequence 是否可以实现之前。或者如果你的意思是f' :: Int -> M Int,那么这两个是action >>= mapM f' vs mapM (>>= f') (unsequence action),我更喜欢不使用unsequence的那个。
  • 我并不是说unsequence 是没有根据的。我只是鼓励您尝试更清晰地描述为什么需要它 - 您的目标越明确,人们就如何实现您的目标提供更好的建议!

标签: haskell monads comonad writer-monad


【解决方案1】:

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

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

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

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

您的Comonad 实例很好(它确实遵循the comonad laws),除了您实际上不需要其中的Monoid 约束。对comonad通常称为Env;请参阅this answer 讨论它的作用。

【讨论】:

  • "unsequence1 有效,因为......它实际上是序列"。我的脑袋从耳朵里漏了出来。这太酷了。
  • @DanielWagner Traversable 是一件美丽的事情:)
  • 感谢您的出色回答!确实令人惊讶的是,两个看似相反的操作都可以映射到sequence
  • @krokodil 矩阵转置函数是它自己的逆函数。
猜你喜欢
  • 2014-07-19
  • 1970-01-01
  • 2015-03-19
  • 2012-10-06
  • 2013-07-12
  • 1970-01-01
  • 2021-08-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多