简体   繁体   English

“读者”monad

[英]The “reader” monad

OK, so the writer monad allows you to write stuff to [usually] some kind of container, and get that container back at the end. 好的,所以编写器monad允许你将东西写入[通常]某种容器,并在最后收回容器。 In most implementations, the "container" can actually be any monoid. 在大多数实现中,“容器”实际上可以是任何幺半群。

Now, there is also a "reader" monad. 现在,还有一个“读者”monad。 This, you might think , would offer the dual operation - incrementally reading from some kind of container, one item at a time. 您可能认为 ,这将提供双重操作 - 从某种容器逐步读取,一次一个项目。 In fact, this is not the functionality that the usual reader monad provides. 实际上,这不是通常的读者monad提供的功能。 (Instead, it merely offers easy access to a semi-global constant.) (相反,它只是提供了对半全局常量的轻松访问。)

To actually write a monad which is dual to the usual writer monad, we would need some kind of structure which is dual to a monoid. 要真正写一个单子双重平时作家单子,我们就需要某种结构是双重的独异。

  1. Does anybody have any idea what this dual structure might be? 有没有人知道这种双重结构可能是什么?
  2. Has anybody written this monad? 有没有人写过这个monad? Is there a well-known name for it? 它有一个众所周知的名字吗?

The dual of a monoid is a comonoid. 幺半群的双重是共生的。 Recall that a monoid is defined as (something isomorphic to) 回想一下monoid被定义为(某些同构的)

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

with these laws 有这些法律

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

thus 从而

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

some standard operations are needed 需要一些标准操作

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

with laws like 有法律的

idL $ first delete $ (split x) = x
idR $ second delete $ (split x) = x
assoc $ first split (split x) = second split (split x)

This typeclass looks weird for a reason. 由于一个原因,这个类型类看起来很怪异。 It has an instance 它有一个实例

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

in Haskell, this is the only instance. 在Haskell中,这是唯一的例子。 We can recast reader as the exact dual of writer, but since there is only one instance for comonoid, we get something isomorphic to the standard reader type. 我们可以将读者重新定义为作者的精确对偶,但由于只有一个comonoid实例,我们得到了与标准读者类型同构的东西。

Having all types be comonoids is what makes the category "Cartesian" in "Cartesian Closed Category." 所有类型都是共生类,这就是“笛卡尔封闭范畴”中的类别“笛卡尔”。 "Monoidal Closed Categories" are like CCCs but without this property, and are related to substructural type systems. “Monoidal Closed Categories”类似于CCC但没有这个属性,并且与子结构类型系统有关。 Part of the appeal of linear logic is the increased symmetry that this is an example of. 线性逻辑的部分吸引力在于增加了对称性,这是一个例子。 While, having substructural types allows you to define comonoids with more interesting properties (supporting things like resource management). 而具有子结构类型允许您定义具有更有趣属性的comonoids(支持资源管理等事情)。 In fact, this provides a framework for understand the role of copy constructors and destructors in C++ (although C++ does not enforce the important properties because of the existence of pointers). 实际上,这提供了一个框架,用于理解C ++中复制构造函数和析构函数的作用(尽管由于指针的存在,C ++不强制执行重要的属性)。

EDIT: Reader from comonoids 编辑: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

note that in the above code every variable is used exactly once after binding (so these would all type with linear types). 请注意,在上面的代码中,每个变量在绑定后只使用一次(因此这些变量都将使用线性类型)。 The monad law proofs are trivial, and only require the comonoid laws to work. monad法律证明是微不足道的,只要求共生法律起作用。 Hence, Reader really is dual to Writer . 因此, Reader真的是Writer双重身份。

I'm not entirely sure of what the dual of a monoid should be, but thinking of dual (probably incorrectly) as the opposite of something (simply on the basis that a Comonad is the dual of a Monad, and has all the same operations but the opposite way round). 我不完全确定幺半群的双重性应该是什么,但是想到双重(可能是错误的)与某事物相反(仅仅是因为Comonad是Monad的双重身份,并且具有所有相同的操作但相反的方式)。 Rather than basing it on mappend and mempty I would base it on: 而不是基于mappendmempty我会基于:

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

If we specialise f to a list here, we get: 如果我们将f专门化为一个列表,我们得到:

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

This seems to me to contain all of the monoid class, in particular. 在我看来,这尤其包含了所有的monoid类。

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

So, then I guess the dual of this different monoid class would be: 那么,我猜这个不同的monoid类的对偶是:

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

This is a lot like the monoid factorial class that I have seen on hackage here . 这是很多像我看到的hackage的幺因子类在这里

So on this basis, I think the 'reader' monad you describe would be a supply monad . 所以在此基础上,我认为你描述的'读者'单子会是供应单子 The supply monad is effectively a state transformer of a list of values, so that at any point we can choose to be supplied with an item from the list. 供应monad实际上是值列表的状态转换器,因此在任何时候我们都可以选择从列表中提供项目。 In this case, the list would be the result of unfold.supply monad 在这种情况下,列表将是unfold.supply monad的结果

I should stress, I am no Haskell expert, nor an expert theoretician. 我应该强调,我不是哈斯克尔专家,也不是专家理论家。 But this is what your description made me think of. 但这就是你的描述让我想到的。

Supply is based on State, which makes it suboptimal for some applications. 供应基于State,这使得它对某些应用来说不是最理想的。 For example, we might want to make an infinite tree of supplied values (eg randoms): 例如,我们可能想要创建一个无限的提供值树(例如randoms):

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

But since Supply is based on State, all the labels will be bottom except for the ones one the leftmost path down the tree. 但由于Supply基于State,所以所有标签都是底部,除了树下最左边的路径。

You need something splittable (like in @PhillipJF's Comonoid ). 你需要一些可拆分的东西(比如@ PhillipJF的Comonoid )。 But there is a problem if you try to make this into a Monad: 但是如果你试图将它变成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''

Because the monad laws require f >>= return = f , so that means that r'' = r in the definition of (>>=) .. But, the monad laws also require that return x >>= f = fx , so r' = r as well. 因为monad定律需要f >>= return = f ,所以这意味着在(>>=)的定义中r'' = r ..但是,monad定律还要求return x >>= f = fx ,所以r' = r也是如此。 Thus, for Supply to be a monad, split x = (x,x) , and thus you've got the regular old Reader back again. 因此,对于Supply是一个monad, split x = (x,x) ,这样你就可以重新获得常规的旧Reader了。

A lot of monads that are used in Haskell aren't real monads -- ie they only satisfy the laws up to some equivalence relation . 在Haskell中使用的很多monad都不是真正的monad - 即它们只满足一些等价关系的规律。 Eg many nondeterminism monads will give results in a different order if you transform according to the laws. 例如,如果根据法律进行转换,许多非确定性monad将以不同的顺序给出结果。 But that's okay, that's still monad enough if you're just wondering whether a particular element appears in the list of outputs, rather than where . 但是没关系,如果你只是想知道某个元素是否出现在输出列表中而不是在哪里 ,那么它仍然是monad。

If you allow Supply to be a monad up to some equivalence relation, then you can get nontrivial splits. 如果你允许Supply是一个等价关系的monad,那么你可以得到非常重要的分裂。 Eg value-supply will construct splittable entities which will dole out unique labels from a list in an unspecified order (using unsafe* magic) -- so a supply monad of value supply would be a monad up to permutation of labels. 例如, 价值供应将构建可拆分的实体,这些实体将以未指定的顺序(使用unsafe*魔法)从列表中分配出独特的标签 - 因此供应单值的供应将是标签排列的单一元素。 This is all that is needed for many applications. 这就是许多应用程序所需要的。 And, in fact, there is a function 事实上,有一个功能

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

which abstracts over this equivalence relation to give a well-defined pure interface, because the only thing it allows you to do to labels is to see if they are equal, and that doesn't change if you permute them. 它抽象了这个等价关系,给出了一个明确定义的纯接口,因为它允许你对标签做的唯一事情是看它们是否相等,如果你置换它们就不会改变。 If this runSupply is the only observation you allow on Supply , then Supply on a supply of unique labels is a real monad. 如果这runSupply是你让上唯一的观察Supply ,则Supply独特标签的供应是一个真正的单子。

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

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