Applicative / Monad实例在多大程度上唯一确定?
如所描述的这个问题/答案, Functor
实例是唯一确定的,如果它们存在的话。
对于列表,有两个众所周知的Applicative实例: []
和ZipList
。 因此, Applicative不是唯一的 (另请参阅GHC可以派生函子和适用于monad变换器的实例吗?为什么没有-XDeriveApplicative
扩展?)。 然而, ZipList
需要无限列表,因为它的pure
无限期地重复给定的元素。
Applicative
实例? 再进一步,如果我们可以将[]
和ZipList
到Monad,我们就会有一个例子,monad并不是由数据类型和它的Functor唯一确定的。 唉,只有我们限制自己的无限列表(流)时, ZipList
才有Monad实例。 并且[]
return
创建一个单元素列表,因此它需要有限列表。 因此:
如果存在两个或更多不同实例的示例,则会出现明显的问题,如果它们必须/可以具有相同的应用实例:
最后,我们可以为Alternative / MonadPlus询问相同的问题。 由于存在两套不同的MonadPlus法则,这一事实很复杂。 假设我们接受其中一套法则(并且对于Applicative我们接受右/左分配/吸收,也请参阅这个问题),
如果上述任何一项都是独一无二的,那么我有兴趣知道为什么,有一点证明。 如果没有,反例。
首先,由于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
(唉,列表需要是有限的,否则null
检查将永远不会结束,并且会破坏join . fmap return == id
monad law。)
这有相同的值join
/ concat
上列出的名单矩形,所以可以得到同样的Applicative
。 据我记得,事实证明,前两个单子定律是自动的,你只需要检查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
唯一确定是独特的,正如(这与非相关很远)一样,许多集合以多种方式扩展到monoids。
在这个答案中,我设置了为非空列表找到至少四个不同的有效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
在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)
其中输出形状是从输入形状计算出来的,然后通过从输入位置拾取元素来填充输出位置。
(如果你是彼得·汉考克,你可以对其中的一切进行比喻,形状是命令;位置是响应;容器态射是一种设备驱动程序,以一种方式翻译命令,然后响应另一种命令。)
每个容器态射都给你一个多态函数,但是相反的情况也是如此。 鉴于这样一个米,我们可以采取
(f s, g s) = m (s, id)
也就是说,我们有一个表示定理,说两个容器之间的每个多态函数都是由这样一个f
, g
-pair给出的。
Applicative
怎么样? 我们有点迷路,建立了所有这些机器。 但它是值得的。 当monads和applicatives的底层函数是容器时,多态函数pure
和<*>
, return
和join
必须可以用容器态射的相关概念来表示。
我们首先使用应用程序,使用他们的monoidal演示。 我们需要
unit : () -> (S <| P) ()
mult : forall X, Y. ((S <| P) X, (S <| P) Y) -> (S <| P) (X, Y)
形状从左到右的地图要求我们交付
unitS : () -> S
multS : (S, S) -> S
所以看起来我们可能需要一个monoid。 当你检查应用法律时,你发现我们需要一个幺半群。 装备具有应用结构的容器正在精确地改变其形状上的幺半群结构,并具有合适的位置操作。 没有什么换做是unit
(因为没有源位置chocie),但对于mult
,我们需要whenenver
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
它是不平衡的,因为我们在右边得到了一堆形状,而不仅仅是一个。 如果我们切换到汉考克的解释,我们正在为命令定义一种顺序组合,在那里我们确实根据第一个响应来选择第二个命令,就像我们在电传中进行交互一样。 更多的几何学上,我们正在解释如何将树的两层结合为一个。 如果这种组合物是独特的,那将是非常令人惊讶的。
同样,对于职位,我们必须以一致的方式将单个输出职位映射到配对。 这对Monad来说更复杂:我们首先选择一个外部位置(响应),然后我们必须选择一个适合于在第一个位置(在第一个响应之后选择)找到的形状(命令)的内部位置(响应)。
我很想和Tarmo的工作联系,了解细节,但它似乎还没有上街。 他实际上已经使用这种分析来枚举所有可能的monad结构以用于多个潜在容器的选择。 我期待着这篇论文!
编辑。 通过对另一个答案表示敬意,我应该观察到,当处处P s = ()
,则(S <| P) X ~= (S, X)
和monad / applicative结构恰好相互重合, S
上的幺半群结构。 也就是说,对于作家单子来说,我们只需要选择形状级别的操作,因为在每种情况下,只有一个值的位置。
上一篇: To what extent are Applicative/Monad instances uniquely determined?
下一篇: Can GHC derive Functor and Applicative instances for a monad transformer?