“读者”单子(The “reader” monad)

2019-08-17 14:58发布

OK,所以笔者单子让你写的东西[通常]某种容器中,并获得该容器回到了终点。 在大多数实现中,“容器”,实际上是任何幺。

现在,也有“读者”的单子。 这一点, 你可能会认为 ,将提供双操作-从某种容器,一个项目在一个时间逐步阅读。 事实上,这不是一般的读者单子提供的功能。 (相反,它只是提供了方便地访问到半全局常量。)

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

  1. 没有任何人有任何想法,这是什么二元结构可能是什么?
  2. 有没有人写这个单子? 是否有这一个著名的名字吗?

Answer 1:

我不能完全肯定的双幺半群的应​​该是什么样的,但双思(可能不正确地)为某事(简单的基础上,一个Comonad是双重一个单子的对面,并拥有所有相同的操作但相反的方式圆)。 而不是使其基于mappendmempty我会基于它:

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

如果我们专注F到这里的列表,我们得到:

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

这在我看来,包含所有的幺半群类的,尤其如此。

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

所以,后来我想这双半群不同类的将是:

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

这是很多像我看到的hackage的幺因子类在这里 。

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

我要强调,我不是专家哈斯克尔,也不是专家理论家。 但是,这是你的描述什么我想到的。



Answer 2:

双幺半群是一个comonoid。 回想一下,幺被定义为(东西同构)

 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封闭式类别”像幼儿中心,但没有这个属性,并与亚结构类型​​系统。 线性逻辑的吸引力的部分是增加的对称性,这是一个例子。 同时,其子结构类型允许你定义更有趣的特性(支持之类的资源管理)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

注意,在上面的代码的每个变量是结合后使用一次(因此这些将所有类型与线性类型)。 单子法律证明是微不足道的,只需要comonoid法律工作。 因此, Reader真的是偶的Writer



Answer 3:

供应是根据国家,这使得它最理想的某些应用。 例如,我们可能想使提供的值(如随机量)的无限树:

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

但由于供应是根据国家,所有的标签将是除了那些一个在树中最左边的路径底部。

你需要什么东西裂开的(如在@ PhillipJF的Comonoid )。 但是有一个问题,如果你试图使之成为一个单子如下:

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''

因为单子法律要求f >>= return = f ,这样就意味着r'' = r的定义中(>>=)但是,单子法律还要求return x >>= f = fx ,因此r' = r为好。 因此,对于Supply是一个单子, split x = (x,x)因此,你有定期的老Reader回来。

很多单子了在Haskell中使用的都不是真正的单子-也就是说,它们只能满足法律高达一些等价关系 。 如果根据法律变换如许多非确定性单子会给以不同的顺序结果。 不过没关系,这仍然不够单子如果你只是想知道一个特定元素是否出现在输出列表中,而不是在那里

如果允许Supply成为一个单子了一些等价关系,那么你就可以得到平凡的分裂。 例如, 价值供应将构成将施舍从列表中唯一的标签未指定顺序(使用可分开的实体unsafe*魔法) -所以价值供应的供应单子将是一个单子最多标签排列。 这是所有需要用于许多应用。 而且,事实上,有一个函数

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

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



文章来源: The “reader” monad