应用程序组成,单子不组成


110

应用程序组成,单子程序不组成。

以上陈述是什么意思?什么时候比另一个更好?


5
你从哪里得到这句话?查看某些上下文可能会有所帮助。
2011年

@FUZxxl:最近,我在Twitter上的debasishg上多次听到过很多人的来信。
missingfaktor 2011年

3
@stephen泰特利:需要注意的是很多这样的Applicatives为实际上是整个家庭MonadS,即一个用于每个结构可能的“形状”。ZipList不是Monad,但ZipLists是固定长度的。Reader“结构”的大小固定为环境类型的基数是一种方便的特殊情况(或通用吗?)。
CA McCann

3
@CAMcCann如果您以某种形式使Reader单子最多达到同构状态的方式修复形状,则所有这些zippy应用程序(无论它们是截断还是填充)都限于单子。固定容器的形状后,它可以有效地从位置对函数进行编码,例如备忘备忘录。彼得·汉考克(Peter Hancock)称这类函子为“ Naperian”,因为它们遵循对数律。
Pigworker 2011年

4
@stephen tetley:其他示例包括常量monoid应用程序(它是monad的组合,但不是monad的组合)和unit-delay应用程序(最好不要允许join)。
Pigworker 2011年

Answers:


115

如果我们比较类型

(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m =>       m s -> (s -> m t) -> m t

我们获得了将这两个概念区分开的线索。中的那个表示(s -> m t)in (>>=)中的值s可以确定in 中的计算行为m t。Monad允许值和计算层之间的干扰。该(<*>)运营商允许没有这样的干扰:功能和参数的计算不依赖于价值。真的好咬人 比较

miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
  b <- mb
  if b then mt else mf

它使用某种效果的结果在两次计算之间做出决定(例如,发射导弹和签署停战协议),而

iffy :: Applicative a => a Bool -> a x -> a x -> a x
iffy ab at af = pure cond <*> ab <*> at <*> af where
  cond b t f = if b then t else f

它使用的值ab在两个计算at和之间进行选择af,也许已经执行了这两个操作,但这可能是悲剧性的结果。

monadic版本本质上依赖(>>=)从值中选择计算的额外能力,这可能很重要。但是,支持这种能力会使单子难以组成。如果我们尝试建立“双绑定”

(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t)
mns >>>>== f = mns >>-{-m-} \ ns -> let nmnt = ns >>= (return . f) in ???

我们已经走了很远,但是现在我们的图层都混乱了。我们有一个n (m (n t)),所以我们需要摆脱外部n。正如Alexandre C所说,只要有合适的人选,我们就能做到

swap :: n (m t) -> m (n t)

以排列在n向内和join它的其他n

较弱的“双重申请”更容易定义

(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs

因为各层之间没有干扰。

相应地,最好识别出何时确实需要Monads 的额外能力,以及何时可以摆脱所Applicative支持的刚性计算结构。

请注意,顺便说一下,尽管合成单子很困难,但可能超出了您的需求。该类型m (n v)表示使用m-effects 计算,然后使用n-effects 计算为-value v,其中m-effects在n-effects开始之前完成(因此需要swap)。如果您只是想让m-effects与n-effects 交错,那么组合可能就太多了!


3
对于有问题的示例,您声明它“使用ab的值在at和af的两个计算的值之间进行选择,可能已经执行了这两个操作,这可能是悲剧性的结果”。Haskell的懒惰性质是否可以保护您免受此侵害?如果我有list =(\ btf->如果b然后为t,则为f):[],然后执行以下语句:list <*> pure True <*> pure“ hello” <*> pure(错误“ bad”)。 ...我得到“你好”,错误永远不会发生。这段代码并不像monad那样安全或受控制,但是该帖子似乎暗示着应用程序会引起严格的评估。总体来说很棒的帖子!谢谢!
shj 2012年

7
您仍然会同时获得这两种效果,但是纯(错误“错误”)没有任何效果。另一方面,如果尝试iffy(纯True)(纯“ hello”)(错误“ bad”),则会得到miffy避免的错误。此外,如果您尝试使用iffy(纯True)(纯0)[1,2]之类的方法,则会得到[0,0]而不是[0]。应用程序对它们有一种严格的要求,因为它们建立了固定的计算序列,但是如您所观察到的那样,这些计算所产生的仍然很懒散地组合在一起。
养猪工人

是的,对于任何monad mn您都可以始终编写monad转换器mt并使用进行n (m t)操作mt n t?因此,您始终可以使用转换器来编写monad,这会更复杂吗?
罗恩

4
这类变压器经常存在,但据我所知,尚无规范的生成方式。关于如何解决不同单子的交错效果,通常有一个真正的选择,经典的例子是异常和状态。异常回滚状态是否应该更改?两种选择都有自己的位置。话虽这么说,但有一种“自由单子”表示“任意交织”。data Free f x = Ret x | Do (f (Free f x))然后data (:+:) f g x = Inl (f x) | Tnr (g x),再考虑一下Free (m :+: n)。这延迟了如何进行交错的选择。
养猪工人

@pigworker关于懒惰/严格的辩论。我认为,随着applicatives你不能从控制影响范围内的计算,但影响层可以很好地决定不以评估后的值。对于(应用程序)解析器,这意味着如果解析器过早失败,则后续解析器将不会被评估/应用于输入。因为Maybe这意味着早Nothing将抑制对a后一个/后继的评估Just a。这样对吗?
ziggystar,2015年

75

应用程序组成,单子程序不组成。

Monad 确实可以合成,但是结果可能不是Monad 。相反,两个应用程序的组成必然是一个应用程序。我怀疑原始陈述的意图是“适用性构成,而单子性不构成”。改写为“ Applicative根据构成是封闭的,而Monad不是封闭的”。


24
另外,任何两种施用剂都以完全机械的方式组成,而由两个单核的组成所形成的单核对该组成是特定的。
Apocalisp

12
而且单子以其他方式组成,两个单子的乘积是一个单子,只有副产物需要某种分配法则。
爱德华·KMETT

带有@Apocalisp,包括评论,这是最好,最简洁的答案。
保罗·德雷珀

39

如果您有A1A2,那么类型data A3 a = A3 (A1 (A2 a))也可以(您可以以通用方式编写这样的实例)。

另一方面,如果您有monad M1M2则类型data M3 a = M3 (M1 (M2 a))不一定是monad(对于该组合>>=join该组合没有明智的通用实现)。

一个例子可以是类型[Int -> a](在这里我们撰写类型构造[](->) Int,这两者都是单子)。您可以轻松编写

app :: [Int -> (a -> b)] -> [Int -> a] -> [Int -> b]
app f x = (<*>) <$> f <*> x

这可以推广到任何应用程序:

app :: (Applicative f, Applicative f1) => f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)

但是没有明智的定义

join :: [Int -> [Int -> a]] -> [Int -> a]

如果您对此不满意,请考虑以下表达式:

join [\x -> replicate x (const ())]

在提供整数之前,必须以石头设置返回列表的长度,但是正确的长度取决于所提供的整数。因此,join此类型不能存在正确的功能。


1
...所以在函数起作用时避免单子?
安德鲁·库克

2
@andrew,如果您的意思是函子,那么是的,函子更简单,应在足够时使用。请注意,并非总是如此。例如,IO如果没有,Monad将很难编程。:)
Rotsor 2011年

17

不幸的是,我们真正的目标,即单子的构成,要困难得多。..实际上,实际上,我们可以证明,从某种意义上讲,没有办法仅使用两个monad的操作来构造具有上述类型的join函数(请参见附录中的证明概述)。因此,我们可能希望形成构图的唯一方法是,如果有一些附加的结构将这两个组件联系起来。

撰写monad,http: //web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf


4
Tl; dr,耐心的读者:如果可以提供自然的转换,则可以编写monadsswap : N M a -> M N a
Alexandre

@Alexandre C .:我怀疑只是“如果”。并非所有的monad变压器都由直接函子组成来描述。例如,ContT r m a既不是也不m (Cont r a)Cont r (m a),并且StateT s m a大致是Reader s (m (Writer s a))
CA McCann

@CA McCann:我似乎无法从(M monad,N monad,MN monad,NM monad)到达(存在交换:MN-> NM natural)。因此,让我们暂时坚持“如果”(也许答案在论文中,我必须承认我很快就查了一下)
Alexandre C.11年

1
@Alexandre C .:仅仅指定合成是单子还是不够的-您还需要某种方式将两个部分与整体联系起来。的存在swap暗示该组合使两者以某种方式“合作”。另外,请注意,这sequence是某些单子的“交换”的特例。那么是flip,实际上。
CA McCann

7
swap :: N (M x) -> M (N x)我看来,您可以使用returns(适当地fmapped)M在前面插入一个,然后N在后面插入一个N (M x) -> M (N (M (N x))),然后使用join合成的来获取您的M (N x)
猪工

7

分配法解l:MN-> NM足够

保证NM的唯一性 要看到这个,您需要一个单位和一个多人。我将重点关注多位(单位为unit_N unitM)

NMNM - l -> NNMM - mult_N mult_M -> NM

不能保证MN是monad。

但是,当您有了分配法解决方案时,至关重要的观察就会发挥作用

l1 : ML -> LM
l2 : NL -> LN
l3 : NM -> MN

因此,LM,LN和MN是单子。出现的问题是LMN是否为单子(

(MN)L-> L(MN)或N(LM)->(LM)N

我们有足够的结构来制作这些地图。但是,正如Eugenia Cheng所观察到的那样,我们需要一个六边形条件(相当于Yang-Baxter方程的表示形式),以保证这两种结构的均一性。实际上,在六角形条件下,两个不同的单子重合。


9
这可能是一个很好的答案,但它使我头疼不已
丹·伯顿

1
这是因为,使用术语Applicative和haskell标记,这是有关haskell的问题,但答案却不同。
codeshot
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.