“读者”monad

时间:2013-03-14 19:06:24

标签: haskell monads

好的,所以编写器monad允许你把东西写到[通常]某种容器,并在最后收回容器。在大多数实现中,“容器”实际上可以是任何幺半群。

现在,还有一个“读者”monad。这个你可能认为,会提供双重操作 - 从某种容器逐步读取,一次一个项目。实际上,这不是通常的读者monad提供的功能。 (相反,它只是提供了对半全局常量的轻松访问。)

要实际写一个 双重的monad到通常的编写器monad,我们需要某种对monoid的双重结构。

  1. 有没有人知道这种双重结构可能是什么?
  2. 有人写过这个monad吗?是否有一个众所周知的名字?

3 个答案:

答案 0 :(得分:28)

幺半群的双重是共生的。回想一下,monoid定义为(某些同构的)

 class Monoid m where
    create :: () -> m
    combine :: (m,m) -> m

遵守这些法律

 combine (create (),x) = x
 combine (x,create ()) = x
 combine (combine (x,y),z) = combine (x,combine (y,z))

因此

 class Comonoid m where
    delete :: m -> ()
    split :: m -> (m,m)

需要一些标准操作

 first :: (a -> b) -> (a,c) -> (b,c)
 second :: (c -> d) -> (a,c) -> (a,d)

 idL :: ((),x) -> x
 idR :: (x,()) -> x

 assoc :: ((x,y),z) -> (x,(y,z))

有像

这样的法律
idL $ first delete $ (split x) = x
idR $ second delete $ (split x) = x
assoc $ first split (split x) = second split (split x)

这个类型类看起来很奇怪有一个原因。它有一个实例

instance Comonoid m where
   split x = (x,x)
   delete x = ()

在Haskell中,这是唯一的实例。我们可以将读者重新定义为作者的精确对偶,但由于只有一个comonoid实例,我们得到了与标准读者类型同构的东西。

所有类型都是comonoids是“笛卡尔闭合类别”中的类别“笛卡尔”。 “Monoidal Closed Categories”类似于CCC但没有这个属性,并且与子结构类型系统有关。线性逻辑的部分吸引力在于增加了对称性,这是一个例子。而具有子结构类型允许您定义具有更有趣属性的comonoids(支持资源管理等事情)。实际上,这提供了一个框架,用于理解C ++中拷贝构造函数和析构函数的作用(尽管由于指针的存在,C ++没有强制执行重要的属性)。

编辑:comonoids的读者

newtype Reader r x = Reader {runReader :: r -> x}
forget :: Comonoid m => (m,a) -> a
forget = idL . first delete

instance Comonoid r => Monad (Reader r) where
   return x = Reader $ \r -> forget (r,x)
   m >>= f = \r -> let (r1,r2) = split r in runReader (f (runReader m r1)) r2

ask :: Comonoid r => Reader r r
ask = Reader id

请注意,在上面的代码中,每个变量在绑定后只使用一次(因此这些变量都将使用线性类型)。 monad法律证明是微不足道的,只要求共生法律起作用。因此,Reader确实是Writer的双重身份。

答案 1 :(得分:12)

我不完全确定幺半群的双重性应该是什么,但是想到双重(可能是错误的)与某事物相反(仅仅基于Comonad是Monad的双重性,并且全部相同的操作,但相反的方式)。不是基于mappendmempty,而是基于:

fold :: (Foldable f, Monoid m) => f m -> m

如果我们将f专门化为一个列表,我们得到:

fold :: Monoid m => [m] -> m

在我看来,特别是包含了所有的monoid类。

mempty == fold []
mappend x y == fold [x, y]

那么,我猜这个不同的monoid类的对偶是:

unfold :: (Comonoid m) => m -> [m]

这很像我在hackage here上看到的monoid阶乘阶级。

所以在此基础上,我认为你所描述的'读者'monad将是supply monad。供应monad实际上是值列表的状态转换器,因此在任何时候我们都可以选择从列表中提供项目。在这种情况下,列表将是unfold.supply monad的结果

我应该强调,我不是哈斯克尔专家,也不是专家理论家。但这就是你的描述让我想到的。

答案 2 :(得分:3)

供应基于State,这使得它对某些应用程序来说不是最理想的。例如,我们可能想要创建一个无限的提供值树(例如randoms):

tree :: (Something r) => Supply r (Tree r)
tree = Branch <$> supply <*> sequenceA [tree, tree]

但是由于Supply基于State,所以所有标签都是底部,除了树下最左边的路径。

你需要一些可分割的东西(比如@ PhillipJF的Comonoid)。但如果你试图把它变成Monad就会出现问题:

newtype Supply r a = Supply { runSupply :: r -> a }

instance (Splittable r) => Monad (Supply r) where
    return = Supply . const
    Supply m >>= f = Supply $ \r ->
        let (r',r'') = split r in
        runSupply (f (m r')) r''

因为monad法律要求f >>= return = f,所以这意味着r'' = r定义中的(>>=) ..但是,monad法律也要求return x >>= f = f x,所以{ {1}}也是。因此,r' = r成为monad Supply,因此您可以重新使用常规旧版split x = (x,x)

在Haskell中使用的很多monad都不是真正的monad - 即它们只满足达到某种等价关系的规律。例如。如果按照法律进行转换,许多非确定性monad将以不同的顺序给出结果。但是没关系,如果你只想知道是否特定元素出现在输出列表中,而不是其中,那么它仍然是monad。

如果你允许Reader成为一个等价关系的monad,那么你可以得到非常重要的分裂。例如。 value-supply将构建可拆分实体,这些实体将以未指定的顺序从列表中发出唯一标签(使用Supply魔法) - 因此,供应价值供应单元将是标签排列的单一元素。这就是许多应用程序所需要的。而且,事实上,有一个功能

unsafe*

它抽象了这个等价关系,给出了一个定义良好的纯接口,因为它允许你对标签做的唯一事情是看它们是否相等,如果你置换它们就不会改变。如果此runSupply :: (forall r. Eq r => Supply r a) -> a 是您在runSupply上允许的唯一观察,那么Supply上的唯一标签供应就是真正的单身。