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

如所描述的这个问题/答案, Functor实例是唯一确定的,如果它们存在的话。

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

  • 还有其他更好的数据结构示例,它们至少有两个Applicative实例?
  • 是否有这样的例子只涉及有限的数据结构? 也就是说,就像假设Haskell的类型系统区分归纳和共诱导数据类型一样,是否有可能唯一确定Applicative?
  • 再进一步,如果我们可以将[]ZipList到Monad,我们就会有一个例子,monad并不是由数据类型和它的Functor唯一确定的。 唉,只有我们限制自己的无限列表(流)时, ZipList才有Monad实例。 并且[] return创建一个单元素列表,因此它需要有限列表。 因此:

  • Monad实例是否由数据类型唯一确定? 还是有一个数据类型的例子可以有两个不同的Monad实例?
  • 如果存在两个或更多不同实例的示例,则会出现明显的问题,如果它们必须/可以具有相同的应用实例:

  • Monad实例是由Applicative实例唯一确定的,还是有一个Applicative实例可以有两个不同的Monad实例?
  • 是否有一个包含两个不同Monad实例的数据类型的示例,每个实例都有不同的Applicative超级实例?
  • 最后,我们可以为Alternative / MonadPlus询问相同的问题。 由于存在两套不同的MonadPlus法则,这一事实很复杂。 假设我们接受其中一套法则(并且对于Applicative我们接受右/左分配/吸收,也请参阅这个问题),

  • 是由Applicative和MonadPlus by 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)值,所以这些值具有唯一的扩展名。编辑:来想一想, EitherMaybe还是Writer也只有“矩形” m (mx)值,所以它们从ApplicativeMonad的扩展也是唯一的。)

    不过,如果存在两个MonadApplicative ,我不会感到惊讶。

    对于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,由两个组件决定

  • 一组形状,S:Set
  • 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

    在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)
    

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

    Applicative怎么样? 我们有点迷路,建立了所有这些机器。 但它是值得的。 当monads和applicatives的底层函数是容器时,多态函数pure<*>returnjoin必须可以用容器态射的相关概念来表示。

    我们首先使用应用程序,使用他们的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上的幺半群结构。 也就是说,对于作家单子来说,我们只需要选择形状级别的操作,因为在每种情况下,只有一个值的位置。

    链接地址: http://www.djcxy.com/p/33219.html

    上一篇: To what extent are Applicative/Monad instances uniquely determined?

    下一篇: Can GHC derive Functor and Applicative instances for a monad transformer?