Applicative / Monad实例在多大程度上唯一确定?

时间:2015-10-04 08:31:14

标签: haskell monads functor applicative category-theory

如上所述this question/answersFunctor个实例是唯一确定的(如果存在的话)。

对于列表,有两个众所周知的Applicative实例:[]ZipList。所以 Applicative并不是唯一的(另见Can GHC derive Functor and Applicative instances for a monad transformer?Why is there no -XDeriveApplicative extension?)。但是,ZipList需要无限列表,因为pure无限期地重复给定元素。

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

更进一步,如果我们可以将[]ZipList扩展到Monad,我们就有一个例子,其中monad不是由数据类型唯一确定的函子。唉,ZipList有一个Monad实例only if we restrict ourselves to infinite listsstreams)。 return []创建了一个单元素列表,因此需要有限列表。因此:

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

如果有一个包含两个或更多不同实例的示例,如果它们必须/可以具有相同的Applicative实例,则会出现一个明显的问题:

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

最后我们可以为Alternative / MonadPlus提出同样的问题。由于存在两组不同的MonadPlus laws,因此这很复杂。假设我们接受一组法律(对于Applicative,我们接受right/left distributivity/absorption,另请参阅this question),

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

如果上述任何一项都是独一无二的,我有兴趣知道为什么要提示证明。如果没有,反例。

2 个答案:

答案 0 :(得分:23)

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

data M a = M Int a

然后您可以将ApplicativeMonad个实例同构为以下任何一个:

Writer (Sum Int)
Writer (Product Int)

给定Monoid类型的s个实例,另一个具有不同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:对于列表,限制值是所有元素具有相同长度的值,即列表列表用&#34;矩形&#34;形状

(对于Reader monad,其中monadic值的&#34;形状&#34;不变,这些实际上是所有m (m x)值,因此它们具有唯一性编辑:想想看,EitherMaybeWriter也只有&#34;矩形&#34; m (m x)值,所以他们的扩展来自{ {1}} Applicative也是唯一的。)

如果Monad存在两个Applicative,我不会感到惊讶。

对于Monad / Alternative我不记得使用左分布法而不是左抓的实例的任何法,我看到没有什么阻止你只是交换{{ 1}}与MonadPlus。我不知道是否有一个不那么微不足道的变化。

ADDENDUM:我突然想起我已经找到一个(<|>)的示例,其中包含两个flip (<|>) s。即,有限列表。通常的Applicative实例,但您可以通过以下函数替换其Monad(基本上是空列表&#34;传染性&#34;):

Monad []

(唉,列表需要是有限的,否则join检查永远不会完成,这会破坏ljoin xs | any null xs = [] | otherwise = concat xs monad法律。)

这与列表的矩形列表上的null / join . fmap return == id具有相同的值,因此会给出相同的join。我记得,事实证明,前两个monad法则是自动的,你只需要检查concat

答案 1 :(得分:17)

鉴于每个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是唯一确定的,就像(并且这远非无关)许多集合以多种方式扩展到monoid。

this answer中,我设置了为非空列表找到至少四个不同的有效Applicative实例的练习:我不会在这里破坏它,但我会对如何给出一个很大的暗示去打猎。

与此同时,在一些精彩的近期作品中(我几个月前在夏季学校看到过),Tarmo Uustalu展示了一种相当简洁的方法来处理这个问题,至少当底层函子是时容器,在雅培,阿尔滕基希和加尼的意义上。

警告:提前依赖类型!

什么是容器?如果你有依赖类型,你可以统一呈现容器式仿函数F,由两个组件决定

  1. 一组形状,S:Set
  2. S索引的一组位置,P:S - >设置
  3. 直到同构,F X中的容器数据结构由某些形状s的依赖对给出:S,并且某些函数e:P s - &gt; X,告诉你位于每个位置的元素。也就是说,我们定义容器的扩展名

    (S <| P) X = (s : S) * (P s -> X)
    

    (顺便说一句,如果你将->看作反向取幂,那么它看起来很像一个广义的幂级数)。三角形应该提醒你侧面有一个树节点,元素s:S标记顶点,基线代表位置集P s。我们说如果某个仿函数与某些S <| P同构,那么它就是一个容器。

    在Haskell中,您可以轻松地使用S = F (),但构建P可能会占用相当多的类型hackery。但你可以在家里尝试的东西。您会发现容器在所有常用的多项式类型形成操作以及标识下都是关闭的,

    Id ~= () <| \ _ -> ()
    

    组合物,其中整个形状仅由一个外部形状和每个外部位置的内部形状制成,

    (S0 <| P0) . (S1 <| P1)  ~=  ((S0 <| P0) S1) <| \ (s0, e0) -> (p0 : P0, P1 (e0 p0))
    

    以及其他一些东西,尤其是 tensor ,其中有一个外形和一个内部形状(所以&#34;外部&#34;和#34;内部&#34;可以互换)

    (S0 <| P0) (X) (S1 <| P1)   =   ((S0, S1) <| \ (s0, s1) -> (P0 s0, P1 s1))
    

    以便F (X) G表示&#34; F - G的结构 - 结构 - 所有相同的形状&#34;,例如,[] (X) []表示矩形列表清单。但是我离题了

    容器之间的多态函数每个多态函数

    m : forall X. (S0 <| P0) X -> (S1 <| P1) X
    

    可以通过容器态射实现,它以非常特殊的方式从两个组件构建。

    1. 将输入形状映射到输出形状的函数f : S0 -> S1;
    2. 将输出位置映射到输入位置的函数g : (s0 : S0) -> P1 (f s0) -> P0 s0
    3. 我们的多态函数是

      \ (s0, e0) -> (f s0, e0 . g s0)
      

      从输入形状计算输出形状,然后通过从输入位置拾取元素来填充输出位置。

      (如果你是彼得汉考克,你还有另外一个比喻是什么。形状是命令;位置是响应;容器态射是设备驱动程序 ,以一种方式翻译命令,然后响应另一种方式。)

      每个容器态射给你一个多态函数,但反过来也是如此。鉴于这样的m,我们可以采取

      (f s, g s) = m (s, id)
      

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

      Applicative怎么样?我们在整个过程中有点迷失,建造了所有这些机器。但它已经值得。当monad和applicatives的底层函子是容器时,多态函数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没有任何关系(因为没有源位置的chocie),但对于mult,我们需要那个

      multS (s0, s1) = s
      

      我们有

      multP (s0, s1) : P s -> (P s0, P s1)
      

      满足适当的身份和相关性条件。如果我们切换到Hancock的解释,我们为命令定义一个monoid(跳过,分号),在选择第二个命令之前无法查看对第一个命令的响应,就像命令是打卡片组。我们必须能够将对组合命令的响应切换为对各个命令的单独响应。

      因此,形状上的每个幺半群都为我们提供了潜在的应用结构。对于列表,形状是数字(长度),并且有很多幺半群可供选择。即使形状存在Bool,我们也有很多选择。

      Monad怎么样?同时,对于M的monad 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上的幺半群结构。也就是说,对于编写器monad,我们只需要选择形状级操作,因为在每种情况下都只有一个值的位置。