Haskell 结合状态单子和共状态单子

Haskell 结合状态单子和共状态单子,haskell,functional-programming,monads,category-theory,comonad,Haskell,Functional Programming,Monads,Category Theory,Comonad,如何将状态monadS->(A,S)与共状态monad(E->A,E)相结合 我尝试了两种明显的组合S->((E->A,E),S)和(E->S->(A,S),E),但在这两种情况下,我都不知道如何定义操作(返回,提取,…等等)对于组合。如果O或I两个单子同时指向,即使用提取方法,则组合两个单子O和I将产生一个单子。每个科摩纳德都是共同点。如果O和I`都是共点的,那么您有两种不同的“自然”方法来获得单子,这两种方法可能是不等价的 你有: unit_O :: a -> O a join_O :

如何将状态monad
S->(A,S)
与共状态monad
(E->A,E)
相结合


我尝试了两种明显的组合
S->((E->A,E),S)
(E->S->(A,S),E)
,但在这两种情况下,我都不知道如何定义操作(
返回
提取
,…等等)对于组合。

如果
O
I
两个单子同时指向,即使用
提取方法,则组合两个单子
O
I
将产生一个单子。每个科摩纳德都是共同点。如果
O
和I`都是共点的,那么您有两种不同的“自然”方法来获得单子,这两种方法可能是不等价的

你有:

unit_O :: a -> O a
join_O :: O (O a) -> O a
unit_I :: a -> I a
join_I :: I (I a) -> I a
在这里,我添加了
\u O
\u I
后缀,以清晰明了;在实际的Haskell代码中,它们不会出现,因为类型检查器会自己解决这个问题

您的目标是显示
O(io(ia))
是单子。让我们假设
O
是共点的,即有一个函数
extract\u O::O a->a

然后我们有:

unit :: a -> O (I a)
unit = unit_O . unit_I
join :: O (I (O (I a))) -> O (I a)
当然,问题在于实现
join
。我们遵循这一战略:

  • fmap
    在外部
    O
  • 使用
    extract\u O
    获得内部
    O
  • 使用
    join_I
    组合两个
    I
    monad
这让我们想到

join = fmap_O $ join_I . fmap_I extract
要使其工作,还需要定义

newtype MCompose O I a = MCompose O (I a)
并将相应的类型构造函数和解构器添加到上述定义中

另一种选择是使用
extract\u I
而不是
extract\u O
。这个版本更简单:

join = join_O . fmap_O extract_I

这定义了一个新的单子。我假设你可以用同样的方式定义一个新的comonad,但我没有尝试过。正如另一个答案所示,这两个组合
S->((E->a,E),S)
(E->S->(a,S),E)
同时拥有Monad和Comonad实例。事实上,给出Monad/Comonad实例是 相当于给出一个 拟幺半群结构。其要点∀r、 r->f(r)或其同点∀r、 f(r)->r,至少在经典中, 非建设性的感觉(我不知道建设性的答案)。这一事实表明,实际上 函子f很有可能 如果它的点和余点是非平凡的,则它可以是单子和余子

然而,真正的问题是这样构造的Monad/Comonad实例是否具有自然属性 计算/范畴意义。在这种情况下,我会说“不”,因为你似乎没有 关于如何以适合您计算需要的方式组合它们的先验知识

合成两个(共)单体的标准分类方法是通过附加。让我总结一下你的情况:

      Fₑ         Fₛ
      -->       -->
Hask  ⊣   Hask  ⊣ Hask
      <--       <--
      Gₑ         Gₛ

Fₜ(a) = (a,t)
Gₜ(a) = (t->a)
现在您可以看到状态monad(s->(a,s))≃ (s->a,s->s)是G的组成部分ₛFₛ 还有costate comonad 是F吗ₑGₑ. 这个附加说明说Hask可以被解释为(co)状态(co)代数的模型

现在,“附属品构成”例如

FₛFₑ(x) -> y ≃
Fₑ(x)   -> Gₛ(y) ≃
x       -> GₑGₛ(y)
所以FₛFₑ ⊣ GₑGₛ. 这就产生了一对单子和一对单子,即

T(a) = GₑGₛFₛFₑ(a)
     = GₑGₛFₛ(a,e)
     = GₑGₛ(a,e,s)
     = Gₑ(s->(a,e,s))
     = e->s->(a,e,s)
     = ((e,s)->a, (e,s)->(e,s))

G(a) = FₛFₑGₑGₛ(a)
     = FₛFₑGₑ(s->a)
     = FₛFₑ(e->s->a)
     = Fₛ(e->s->a,e)
     = (e->s->a,e,s)
     = ((e,s)->a, (e,s))
T是简单的状态单子和状态(e,s),G是共价态和共价态(e,s),所以 这些确实有非常自然的含义

撰写附加语是一种自然、频繁的数学运算。例如,一个几何态射 topoi(一种在“类型级别”上允许复杂(非自由)结构的笛卡尔闭范畴)被定义为一对附加,只要求其左伴随是左精确的(即保留有限极限)。如果这些拓扑是拓扑空间上的滑轮, 组成附加语仅仅对应于组成(唯一的)连续的基本变化图(在相反的方向),具有非常自然的意义

另一方面,在数学中,直接编写单子/单子似乎是一种非常罕见的实践。 这是因为通常(co)单子被认为是(co)代数理论的载体,而不是一个单子 模型在这种解释中,相应的附加语是模型,而不是单子。问题 组成两种理论需要另一种理论,一种关于如何组成它们的理论。例如 想象一下组成两个幺半群理论。那么你可能会得到至少两个新的理论, 即列表的列表理论,或两种二进制运算分布的环形代数。 两者都不比另一个更自然。这就是“单子不作曲”的意思;这并不是说构图不能是单子,而是说你需要另一种理论来构图

相比之下,撰写附加语自然会产生另一个附加语,因为这样做你就是一个附加语
隐含性指定了构成两个给定理论的规则。因此,通过使用复合附加语的单子,你得到了一个理论,它也规定了复合规则。

你的意思是这应该是单子和复合词?对我来说似乎有点乐观,认为这应该是可能的…没有特别的理由相信(a)单子的成分是单子,(b)科摩纳的成分是科摩纳的,或者(c)任何类型都是单子和科摩纳的(注意,身份是,注意这是多么极端)。因此,期望单子和辅音的组合是单子或辅音,更不用说两者都是非常冒险的。“分配定律将适用于状态共状态与共状态相同的情况,不幸的是,这既不正确也没有帮助。@Bob,在不同的上下文中使用它们,因为它们都有什么好处?”?你的问题听起来像“即使我不能将它们结合起来,我如何编写一个同时使用
Bool
Int
的函数程序?”当你谈到单子和复数之间的分配规律时,你会问
T(a) = GₑGₛFₛFₑ(a)
     = GₑGₛFₛ(a,e)
     = GₑGₛ(a,e,s)
     = Gₑ(s->(a,e,s))
     = e->s->(a,e,s)
     = ((e,s)->a, (e,s)->(e,s))

G(a) = FₛFₑGₑGₛ(a)
     = FₛFₑGₑ(s->a)
     = FₛFₑ(e->s->a)
     = Fₛ(e->s->a,e)
     = (e->s->a,e,s)
     = ((e,s)->a, (e,s))