[英]What is the use of Applicative/Monad instances for Sum and Product?
[英]To what extent are Applicative/Monad instances uniquely determined?
如本问题所述, Functor
实例是唯一确定的(如果存在)。
对于列表,有两个众所周知的Applicative实例: []
和ZipList
。 因此, Applicative不是唯一的 (另请参见GHC可以为monad转换器派生Functor和Applicative实例吗? 为什么没有-XDeriveApplicative
扩展? )。 但是, ZipList
需要无限列表,因为其pure
无限期地重复给定元素。
Applicative
实例的数据结构示例,也许是更好的示例? 更进一步,如果我们可以将[]
和ZipList
都扩展为Monad,我们将有一个示例,其中monad不是由数据类型及其Functor唯一地确定的。 ZipList
只有当我们将自己限制在无限列表 ( 流 )中时, ZipList
才具有Monad实例。 return
[]
会创建一个单元素列表,因此它需要有限列表。 因此:
在有两个或多个不同实例的示例的情况下,如果它们必须/可以具有相同的Applicative实例,就会出现一个明显的问题:
最后,我们可以对Alternative / MonadPlus提出相同的问题。 由于存在两种不同的MonadPlus法则 ,这使情况变得复杂。 假设我们接受其中一组定律(对于应用,我们接受右/左分布/吸收 ,另请参见此问题 ),
如果以上任何一个都不是唯一的,我很想知道为什么,以得到一个提示。 如果不是,则为反例。
首先,由于Monoid
不是唯一的,所以Writer
Monad
或Applicative
都不一样。 考虑
data M a = M Int a
那么您可以将Applicative
和Monad
实例同构为以下任意一个:
Writer (Sum Int)
Writer (Product Int)
给定类型s
的Monoid
实例,另一个具有不同Applicative
/ Monad
实例的同构对是:
ReaderT s (Writer s)
State s
至于将一个Applicative
实例扩展到两个不同的Monad
,我不记得任何示例。 但是,当我试图完全说服ZipList
是否真的不能成为Monad
,我发现以下对Monad
严格限制:
join (fmap (\x -> fmap (\y -> f x y) ys) xs) = f <$> xs <*> ys
但是,这并不能为所有值提供join
:对于列表,限制值是所有元素都具有相同长度的值,即具有“矩形”形状的列表的列表。
(对于Reader
monad,其monadic值的“形状”没有变化,实际上它们都是m (mx)
值,因此它们确实具有唯一的扩展名。编辑:来思考一下, Either
, Maybe
和Writer
也只有“矩形” m (mx)
值,因此它们从Applicative
到Monad
的扩展名也是唯一的。)
但是,如果存在带有两个Monad
的Applicative
,我不会感到惊讶。
对于Alternative
/ MonadPlus
,对于使用“左分布”定律而不是“左捕获” 定律的实例, 我无法回忆起任何定律 ,我看不出有什么可以阻止您仅将(<|>)
与flip (<|>)
交换。 我不知道是否有比较小的变化。
附录:我突然想起我找到了一个示例Applicative
有两个Monad
秒。 即,有限列表。 这是通常的Monad []
实例,但是您可以用以下函数替换其join
(基本上使空列表具有“感染性”):
ljoin xs
| any null xs = []
| otherwise = concat xs
(A,列表必须是有限的,因为否则null
检查将永远不会完成,并且会破坏join . fmap return == id
monad law。)
它与矩形列表中的join
/ concat
具有相同的值,因此具有相同的Applicative
。 我记得,事实证明,前两个monad定律是自动产生的,您只需要检查ljoin . ljoin == ljoin . fmap ljoin
ljoin . ljoin == ljoin . fmap ljoin
ljoin . ljoin == ljoin . fmap ljoin
。
鉴于每个Applicative
都有一个Backwards
对应项,
newtype Backwards f x = Backwards {backwards :: f x}
instance Applicative f => Applicative (Backwards f) where
pure x = Backwards (pure x)
Backwards ff <*> Backwards fs = Backwards (flip ($) <$> fs <*> ff)
对Applicative
唯一地确定是不寻常的,就像许多集合以多种方式扩展到类半体一样(这是很不相关的)。
在此答案中 ,我将练习为非空列表至少查找四个不同的有效Applicative
实例:我不会在这里宠坏它,但会为如何狩猎提供很大的提示。
同时,在最近的一些出色的工作中(我几个月前在一家暑期学校看到的),Tarmo Uustalu展示了一种解决此问题的巧妙方法,至少在底层函子是容器的情况下 ,雅培,阿尔滕基希和加尼。
警告:前面的依赖类型!
什么是容器? 如果您要处理依赖类型,则可以由两个组件确定,均匀地呈现类似容器的函子F
直到同构为止,FX中的容器数据结构由某些形状s:S和一些函数e:P s-> X的相关对提供,这些告诉您位于每个位置的元素。 也就是说,我们定义了容器的扩展名
(S <| P) X = (s : S) * (P s -> X)
(顺便说一句,如果您将->
读为反幂,它看起来很像广义幂级数)。 三角形应该让您想起树形节点的侧面,元素s:S标记顶点,基线表示位置集P s。 我们说某些函子是容器,如果它与某些S <| P
同构S <| P
S <| P
。
在Haskell中,您可以轻松地采用S = F ()
,但是构造P
可能会占用很多类型黑客。 但这是您可以在家尝试的方法。 您会发现容器在所有常见的多项式类型形成操作以及标识,
Id ~= () <| \ _ -> ()
构图,其中整个形状仅由一个外部形状和每个外部位置的内部形状组成,
(S0 <| P0) . (S1 <| P1) ~= ((S0 <| P0) S1) <| \ (s0, e0) -> (p0 : P0, P1 (e0 p0))
还有其他一些东西,特别是张量 ,那里有一个外部形状和一个内部形状(因此“外部”和“内部”可以互换)
(S0 <| P0) (X) (S1 <| P1) = ((S0, S1) <| \ (s0, s1) -> (P0 s0, P1 s1))
因此, F (X) G
表示“ G
结构的F
结构-相同形状”,例如[] (X) []
表示矩形列表。 但是我离题
容器之间的多态函数每个多态函数
m : forall X. (S0 <| P0) X -> (S1 <| P1) X
可以通过容器形态来实现, 容器形态由两个组件以非常特殊的方式构造。
f : S0 -> S1
将输入形状映射到输出形状; g : (s0 : S0) -> P1 (f s0) -> P0 s0
将输出位置映射到输入位置。 那么我们的多态函数是
\ (s0, e0) -> (f s0, e0 . g s0)
其中从输入形状计算出输出形状,然后通过从输入位置拾取元素来填充输出位置。
(如果您是Peter Hancock,那么您对正在发生的事情有一个完全的比喻。形状是命令;位置是响应;容器形态是设备驱动程序 ,一种方式翻译命令,然后另一种方式响应。)
每个容器的态素都为您提供了一个多态函数,但反之亦然。 给定这样一个m,我们可能会
(f s, g s) = m (s, id)
也就是说,我们有一个表示定理 ,说两个容器之间的每个多态函数都由这样一个f
, g
对给出。
那么Applicative
呢? 在构建所有这些机器的过程中,我们有点迷路了。 但它是值得的。 当monads和appadatives的基础函子为容器时,多态函数pure
和<*>
, return
和join
必须由容器态射的相关概念表示。
首先,让我们使用应用程序的半形形式。 我们需要
unit : () -> (S <| P) ()
mult : forall X, Y. ((S <| P) X, (S <| P) Y) -> (S <| P) (X, Y)
形状的从左到右的地图要求我们提供
unitS : () -> S
multS : (S, S) -> S
所以看起来我们可能需要一个半身像。 当您检查适用法律时,您会发现我们恰恰需要一个半同等式。 为容器配备可应用的结构是通过适当的位置尊重操作来精确地将单面体结构细化为其形状。 unit
无关(因为没有源位置的选择),但是对于mult
,我们需要在
multS (s0, s1) = s
我们有
multP (s0, s1) : P s -> (P s0, P s1)
满足适当的身份和关联性条件。 如果切换到汉考克的解释,我们将为命令定义一个等分线(跳过,分号),在选择第二个命令之前,无法查看对第一个命令的响应,就像命令是一副打孔卡一样。 我们必须能够将对组合命令的响应分成对单个命令的单个响应。
因此,形状上的每个monoid都为我们提供了潜在的应用结构。 对于列表,形状是数字(长度),并且有很多可供选择的monoid。 即使形状存在于Bool
,我们也有很多选择。
那Monad
呢? 同时,对于M〜 M ~= S <| P
单子M
M ~= S <| P
。 我们需要
return : Id -> M
join : M . M -> M
首先看形状,这意味着我们需要一种偏斜的类半体。
return_f : () -> S
join_f : (S <| P) S -> S -- (s : S, P s -> S) -> S
这是不平衡的,因为我们在右侧获得了很多形状,而不仅仅是一个。 如果切换到汉考克的解释,我们将为命令定义一种顺序组成,在这种情况下,我们会根据第一个响应选择第二个命令,就像在电传打字机上进行交互一样。 从几何角度上讲,我们正在解释如何将树的两层粘合为一层。 如果这种组合物是独特的,那将是非常令人惊讶的。
同样,对于位置,我们必须以连贯的方式将单个输出位置映射为对。 这对于单子来说比较棘手:我们首先选择一个外部位置(响应),然后我们必须选择一个适合于在第一个位置(在第一个响应之后选择)的形状(命令)的内部位置(响应)。
我很想链接到Tarmo的工作中,以获取详细信息,但似乎还没有出现。 他实际上已经使用此分析来枚举用于基础容器的几种选择的所有可能的monad结构。 我期待着这篇论文!
编辑。 通过尊重其他答案,我应该观察到,当无处不在P s = ()
,则(S <| P) X ~= (S, X)
与monad /应用结构彼此完全一致,并且与S
上的monoid结构。 也就是说,对于编写器monad,我们只需要选择形状级别的操作,因为在每种情况下,值的位置恰好只有一个位置。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.