繁体   English   中英

在什么程度上唯一确定了Applicative / Monad实例?

[英]To what extent are Applicative/Monad instances uniquely determined?

本问题所述, Functor实例是唯一确定的(如果存在)。

对于列表,有两个众所周知的Applicative实例: []ZipList 因此, Applicative不是唯一的 (另请参见GHC可以为monad转换器派生Functor和Applicative实例吗? 为什么没有-XDeriveApplicative扩展? )。 但是, ZipList需要无限列表,因为其pure无限期地重复给定元素。

  • 是否还有其他至少具有两个Applicative实例的数据结构示例,也许是更好的示例?
  • 是否有仅涉及有限数据结构的此类示例? 也就是说,就像假设的Haskell类型系统区分归纳和共数据类型一样,是否有可能唯一地确定Applicative?

更进一步,如果我们可以将[]ZipList都扩展为Monad,我们将有一个示例,其中monad不是由数据类型及其Functor唯一地确定的。 ZipList 只有当我们将自己限制在无限列表 )中时, ZipList才具有Monad实例。 return []会创建一个单元素列表,因此它需要有限列表。 因此:

  • Monad实例是否由数据类型唯一确定? 还是有一个可以具有两个不同的Monad实例的数据类型示例?

在有两个或多个不同实例的示例的情况下,如果它们必须/可以具有相同的Applicative实例,就会出现一个明显的问题:

  • Monad实例是由Applicative实例唯一确定的,还是存在可以具有两个不同Monad实例的Applicative的示例?
  • 是否有一个数据类型的示例,其中包含两个不同的Monad实例,每个实例都有一个不同的Applicative超级实例?

最后,我们可以对Alternative / MonadPlus提出相同的问题。 由于存在两种不同的MonadPlus法则 ,这使情况变得复杂。 假设我们接受其中一组定律(对于应用,我们接受右/左分布/吸收 ,另请参见此问题 ),

  • “替代方案”是由Applicative唯一确定的,还是MonadPlus由Monad唯一确定,还是有任何反例?

如果以上任何一个都不是唯一的,我很想知道为什么,以得到一个提示。 如果不是,则为反例。

首先,由于Monoid不是唯一的,所以Writer MonadApplicative都不一样。 考虑

data M a = M Int a

那么您可以将ApplicativeMonad实例同构为以下任意一个:

Writer (Sum Int)
Writer (Product Int)

给定类型sMonoid实例,另一个具有不同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)值,因此它们确实具有唯一的扩展名。编辑:来思考一下, EitherMaybeWriter也只有“矩形” m (mx)值,因此它们从ApplicativeMonad的扩展名也是唯一的。)

但是,如果存在带有两个MonadApplicative ,我不会感到惊讶。

对于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

  1. 一组形状,S:集合
  2. 一个S索引的位置集,P:S-> Set

直到同构为止,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

可以通过容器形态来实现, 容器形态由两个组件以非常特殊的方式构造。

  1. 函数f : S0 -> S1将输入形状映射到输出形状;
  2. 函数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)

也就是说,我们有一个表示定理 ,说两个容器之间的每个多态函数都由这样一个fg对给出。

那么Applicative呢? 在构建所有这些机器的过程中,我们有点迷路了。 是值得的。 当monads和appadatives的基础函子为容器时,多态函数pure<*>returnjoin必须由容器态射的相关概念表示。

首先,让我们使用应用程序的半形形式。 我们需要

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.

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