【问题标题】:Why is there no `-XDeriveApplicative` extension?为什么没有 `-XDeriveApplicative` 扩展?
【发布时间】:2013-09-17 22:57:07
【问题描述】:

GHC 有几种有用的语言 extensions 用于机械派生各种常见的 Haskell 类型类(-XDeriveFunctor-XDeriveFoldable-XDeriveTraversable)。似乎Applicative 是另一个经常需要并且经常容易派生的类。对于包含a 类型槽的简单记录,例如,

data SimpleRecord a = Simple a a a

Applicative 实例是平凡派生的,

instance Applicative SimpleRecord where
    pure x = Simple x x x
    Simple a1 b1 c1 <*> Simple a2 b2 c2 = Simple (a1 a2) (b1 b2) (c1 c2)

即使在稍微困难的情况下,一些 a 值被埋在其他应用函子中,例如,

data MyRecord f a = MyRecord (f a) a

一个合理的例子很容易写出来,

instance (Applicative f) => Applicative (MyRecord f) where
    pure x = MyRecord (pure x) x
    MyRecord a1 b1 <*> MyRecord a2 b2 = MyRecord (a1 <*> a2) (b1 b1)

为什么不存在实现这些机械实例的-XDeriveApplicative 扩展?即使是derivegeneric-derive 软件包也明显缺乏Applicative 支持。是否存在阻止这些实例通常有效的理论问题(除了可能威胁FunctorFoldableTraversable 扩展的那些原因)?

【问题讨论】:

    标签: haskell ghc deriving derivingvia


    【解决方案1】:

    对于遵循函子定律的给定数据类型,最多有一个Functor 实例。例如,map 是列表中 fmap 的唯一合法实现:

    fmap id      == id
    fmap (f . g) == fmap f . fmap g
    

    Applicative的合法实例可能不止一个,这不一定是显而易见的。

    pure id <*> v              == v
    pure (.) <*> u <*> v <*> w == u <*> (v <*> w)
    pure f <*> pure x          == pure (f x)
    u <*> pure y               == pure ($ y) <*> u
    

    对于列表,&lt;*&gt; 的行为可能与 \fs xs -&gt; concatMap (\f -&gt; map f xs) fszipWith ($) 类似,但尚不清楚编译器应该选择哪一种。

    【讨论】:

    • pure 的情况下,事情似乎很明确(您只有一个a,并且可以明确地将其插入到结构中,假设每个组成函子都是适用的。在&lt;*&gt; 的情况下是排序的问题,但简单地为单个构造函数类型应用相应的字段似乎是完全合理的(通常是用户的意图)。对于任何更复杂的(包括 ADT),编译器可以简单地拒绝派生实例。
    • 同样的反对意见也可以用来对付-XDeriveTraversable,不是吗? Traversable 定义指出,动作应该按“从左到右”的顺序排列,尽管我仍然觉得这意味着什么并不总是很清楚。
    • Ord 的派生方法也有很多,只要明确规定派生的作用就没有问题。
    • @bgamari 实际上即使pure 也不是明确的。对于列表的标准应用函子 pure 给了你一个单例列表,但是为了使应用法则起作用,ZipList pure 必须给你注入值的无限重复列表! hackage.haskell.org/packages/archive/base/latest/doc/html/src/…
    • @augustss:一个区别是,在许多情况下,只要使用一致,使用什么排序并不重要。与Applicative 对比,后者主要用于实例的实际属性。
    【解决方案2】:

    为了回应其他人,我没有充分的理由知道为什么我们不能拥有-XDeriveApplicative,我们只是碰巧没有。 FoldableTraversable 的合法实例通常不止一个,我们有一个标志来派生这些实例。有一段时间我们没有关于可遍历定律的真正好故事,但现在we have some。同样,我们仍然没有Foldable 法律(但我认为我们可以,参见here)。

    在不同的“明显”应用程序中,例如向前和向后的应用程序(相对于 &lt;*&gt; 本身,甚至相对于 f a 中的多个 a 如果有的话),然后构建这里建议的应用程序,按句法顺序遍历,似乎是合法的。但是,对于诸如列表之类的递归类型,甚至具有多个构造函数的类型,我们对有效应用程序的选择以有趣的方式开花。对于 listlike 常规递归类型,明显的应用选择自然是“zipLike”一次,因为它以自然的方式概括了非递归情况,将结构与结构匹配。对于具有多个构造函数的 sum 类型,“显而易见”的选择更难定义。

    在我看来,一个完全合理的 applicative 派生将按照这里的建议工作,在只有一个构造函数的类型(包括递归类型)的语法顺序上。在多个构造函数的情况下,失败似乎是合理的。

    另一方面,虽然 Foldable 和 Traversable 似乎经常以它们的“明显”形式出现,但与有趣的应用程序相比,我们希望定义多少次“明显”应用程序对我来说不是很清楚。我的直觉告诉我,这个功能很少被使用,而且可能根本不经常有用。

    【讨论】:

      【解决方案3】:

      GHC 基础的下一个版本将包括

      • newtype Generically a = Generically a
      • newtype Generically1 f a = Generically1 (f a)

      GHC.Generics.

      这允许直接派生这些实例

      {-# Language DeriveGeneric #-}
      {-# Language DerivingVia #-}
      {-# Language DerivingStrategies #-}
      ..
      
      import GHC.Generics
      
      data SimpleRecord a = Simple a a a
       deriving
       stock Generic1
      
       deriving (Functor, Applicative)
       via Generically1 SimpleRecord
      
      data MyRecord f a = MyRecord (f a) a
       deriving
       stock Generic1
      
       deriving (Functor, Applicative)
       via Generically1 (MyRecord f)
      

      【讨论】:

      • 其他评论员提到了订单。要使用“backwards”效果派生Applicative,您只需通过Backwards (Generically1 SimpleRecord) 派生即可。
      猜你喜欢
      • 2015-05-24
      • 1970-01-01
      • 2012-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-25
      • 2011-11-16
      • 1970-01-01
      相关资源
      最近更新 更多