繁体   English   中英

“读者”monad

[英]The “reader” monad

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

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

要真正写一个单子双重平时作家单子,我们就需要某种结构是双重的独异。

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

幺半群的双重是共生的。 回想一下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实例,我们得到了与标准读者类型同构的东西。

所有类型都是共生类,这就是“笛卡尔封闭范畴”中的类别“笛卡尔”。 “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双重身份。

我不完全确定幺半群的双重性应该是什么,但是想到双重(可能是错误的)与某事物相反(仅仅是因为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的幺因子类在这里

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

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

供应基于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 = fx ,所以r' = r也是如此。 因此,对于Supply是一个monad, split x = (x,x) ,这样你就可以重新获得常规的旧Reader了。

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

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

runSupply :: (forall r. Eq r => Supply r a) -> a

它抽象了这个等价关系,给出了一个明确定义的纯接口,因为它允许你对标签做的唯一事情是看它们是否相等,如果你置换它们就不会改变。 如果这runSupply是你让上唯一的观察Supply ,则Supply独特标签的供应是一个真正的单子。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM