haskell - TypeRep和 "Type"GADT之间的关系

标签 haskell scrap-your-boilerplate

Scrap your boilerplate reloaded中,作者描述了Scrap Your Boilerplate的新表示形式,它应该与原始版本等效。

但是,不同之处在于,它们假设使用GADT编码的一组有限,封闭的“基本”类型

data Type :: * -> * where
  Int :: Type Int
  List :: Type a -> Type [a]
  ...

在原始SYB中,使用类型安全的强制类型转换,并使用Typeable类实现。

我的问题是:
  • 这两种方法之间的关系是什么?
  • 为什么为“SYB重新加载”演示文稿选择了GADT表示形式?
  • 最佳答案

    [我是“SYB Reloaded”论文的作者之一。]

    TL; DR 我们真的只是使用它,因为它对我们来说似乎更漂亮。基于类的Typeable方法更实用。 Spine View 可以与Typeable类结合使用,并且不依赖于Type GADT。

    本文在结论中对此进行了陈述:

    Our implementation handles the two central ingredients of generic programming differently from the original SYB paper: we use overloaded functions with explicit type arguments instead of overloaded functions based on a type-safe cast 1 or a class-based extensible scheme [20]; and we use the explicit spine view rather than a combinator-based approach. Both changes are independent of each other, and have been made with clarity in mind: we think that the structure of the SYB approach is more visible in our setting, and that the relations to PolyP and Generic Haskell become clearer. We have revealed that while the spine view is limited in the class of generic functions that can be written, it is applicable to a very large class of data types, including GADTs.

    Our approach cannot be used easily as a library, because the encoding of overloaded functions using explicit type arguments requires the extensibility of the Type data type and of functions such as toSpine. One can, however, incorporate Spine into the SYB library while still using the techniques of the SYB papers to encode overloaded functions.



    因此,主要是为了清楚起见,我们选择使用GADT进行类型表示。正如Don在回答中所说的那样,此表示形式有一些明显的优点,即它保持有关类型表示形式的静态信息,并且使我们无需任何进一步的魔术,尤其是无需使用,即可实现强制类型转换。的unsafeCoerce。类型索引函数也可以通过在类型上使用模式匹配来直接实现,而不会退回到各种组合器,例如mkQextQ

    事实是,我(我认为是合著者)根本不喜欢Typeable类。 (实际上,我仍然没有,尽管现在终于变得更加严格了,因为GHC为Typeable添加了自动派生功能,使其具有多态性,并最终消除了定义自己的实例的可能性。)此外,Typeable可能还不像现在那样成熟和广为人知,因此似乎很有吸引力,可以使用GADT编码对其进行“解释”。而且,这是我们还考虑将Hastell添加open datatypes,从而减轻GADT关闭的限制的时候。

    因此,总结一下:如果您实际上只需要一个封闭的Universe的动态类型信息,那么我总是会选择GADT,因为您可以使用模式匹配来定义类型索引的函数,而不必依赖unsafeCoerce或高级编译器魔术。但是,如果对于通用编程设置来说,宇宙是很普遍的(这很普遍),那么GADT方法可能是有启发性的,但不切实际,并且使用Typeable是可行的方法。

    但是,正如我们在论文的结论中也指出的那样,选择Type而不是Typeable并不是我们正在做出其他选择的先决条件,即使用Spine View ,我认为这是更重要的,而且实际上是核心的纸。

    论文本身(在第8节中)展示了一种受"Scrap your Boilerplate with Class"论文启发的变体,该论文使用了带有类约束的Spine View 。但是我们也可以进行更直接的开发,下面我将进行演示。为此,我们将使用Typeable中的Data.Typeable,但定义我们自己的Data类,为简单起见,该类仅包含toSpine方法:
    class Typeable a => Data a where
      toSpine :: a -> Spine a
    
    Spine数据类型现在使用Data约束:
    data Spine :: * -> * where
      Constr  :: a -> Spine a
      (:<>:)  :: (Data a) => Spine (a -> b) -> a -> Spine b
    

    函数fromSpine与其他表示形式一样简单:
    fromSpine :: Spine a -> a
    fromSpine (Constr x) = x
    fromSpine (c :<>: x) = fromSpine c x
    
    Data的实例对于诸如Int的平面类型而言是微不足道的:
    instance Data Int where
      toSpine = Constr
    

    对于结构化类型(例如二叉树),它们仍然非常简单:
    data Tree a = Empty | Node (Tree a) a (Tree a)
    
    instance Data a => Data (Tree a) where
      toSpine Empty        = Constr Empty
      toSpine (Node l x r) = Constr Node :<>: l :<>: x :<>: r
    

    然后,本文继续并定义了各种通用函数,例如mapQ。这些定义几乎不变。我们仅获得Data a =>的类约束,其中纸张具有Type a ->的函数参数:
    mapQ :: Query r -> Query [r]
    mapQ q = mapQ' q . toSpine
    
    mapQ' :: Query r -> (forall a. Spine a -> [r])
    mapQ' q (Constr c) = []
    mapQ' q (f :<>: x) = mapQ' q f ++ [q x]
    

    诸如everything之类的高级函数也只会丢失其显式的类型参数(然后实际上看起来与原始SYB完全相同):
    everything :: (r -> r -> r) -> Query r -> Query r
    everything op q x = foldl op (q x) (mapQ (everything op q) x)
    

    就像我在上面说过的,如果现在我们想定义一个通用的求和函数来汇总所有Int出现的情况,我们就不能再进行模式匹配,而必须回到mkQ,但是mkQ纯粹是根据Typeable定义的,并且完全独立于Spine:
    mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r
    (r `mkQ` br) a = maybe r br (cast a)
    

    然后(再次与原始SYB完全相同):
    sum :: Query Int
    sum = everything (+) sumQ
    
    sumQ :: Query Int
    sumQ = mkQ 0 id
    

    对于本文后面的一些内容(例如,添加构造函数信息),还需要做更多的工作,但是可以完成所有工作。因此,使用Spine确实完全不依赖于使用Type

    关于haskell - TypeRep和 "Type"GADT之间的关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15160470/

    相关文章:

    haskell - GHCi 中的严格列表评估

    haskell - 用确定性值替换重复的功能应用程序

    haskell - Haskell中的O(1)循环缓冲区?

    haskell - 是否可以使用 SYB 转换类型?

    haskell - 从类型 `T a` 转换为 `T b`,无需样板

    generics - 如何使用 GHC.Generics(或其他类似框架)构造通用 Functor 实例?

    haskell - 傻瓜的递归方案?

    haskell - Haskell 中的编号输出行

    haskell - 什么是 "Scrap Your Boilerplate"?

    haskell - 使用 GHC.Generics 派生默认实例