代码之家  ›  专栏  ›  技术社区  ›  Carl

用相同的内部表示和最少的样板处理多个类型?

  •  11
  • Carl  · 技术社区  · 14 年前

    当我用Haskell编写更大的程序时,我发现自己经常遇到一个问题。我发现自己经常需要多个不同的类型,它们共享一个内部表示和几个核心操作。

    一种是使用类型类和 GeneralizedNewtypeDeriving

    另一种是用虚类型变量声明类型,然后使用 EmptyDataDecls 为每个不同的用例创建不同的类型。

    每种方法的优缺点是什么?有没有一种技术更接近于实现我想要的功能,即在没有样板代码的情况下提供类型安全性?

    3 回复  |  直到 14 年前
        1
  •  2
  •   Anthony    14 年前

    我对玩具示例进行了基准测试,没有发现这两种方法之间的性能差异,但使用情况确实有点不同。

    newtype 包装器以指示语义上更具体的类型。使用 新类型

    s1 = Specific1 $ General "Bob" 23
    s2 = Specific2 $ General "Joe" 19
    

    其中不同的特定newtypes之间的内部表示相同的事实是透明的。

    类型标记方法几乎总是伴随着表示构造函数隐藏,

    data General2 a = General2 String Int
    

    mkSpecific1 "Bob" 23
    

    部分原因是你需要一些语法上的轻松方式来指示你想要的标签。如果您没有提供智能构造函数,那么客户机代码通常会选择类型注释来缩小范围,例如。,

    myValue = General2 String Int :: General2 Specific1
    

    internalFun :: General2 a -> General2 a -> Int
    internalFun (General2 _ age1) (General2 _ age2) = age1 + age2
    

    新类型 新类型 包装带 GeneralizedNewtypeDeriving 好好工作。但是,如果您打算采用智能构造函数来处理不透明表示,那么我通常更喜欢幻影类型。

        2
  •  3
  •   sclv    14 年前

    还有另一个简单的方法。

    data MyGenType = Foo | Bar
    
    op :: MyGenType -> MyGenType
    op x = ...
    
    op2 :: MyGenType -> MyGenType -> MyGenType
    op2 x y = ...
    
    newtype MySpecialType {unMySpecial :: MyGenType}
    
    inMySpecial f = MySpecialType . f . unMySpecial
    inMySpecial2 f x y = ...
    
    somefun = ... inMySpecial op x ...
    someOtherFun = ... inMySpecial2 op2 x y ...
    

    newtype MySpecial a = MySpecial a
    instance Functor MySpecial where...
    instance Applicative MySpecial where...
    
    somefun = ... fmap op x ...
    someOtherFun = ... liftA2 op2 x y ...
    

    我认为,如果你想在任何频率下使用你的普通类型“裸体”,并且只是有时想标记它,那么这些方法会更好。另一方面,如果您通常希望使用带标记的方法,那么幻像类型方法更直接地表达您想要的内容。

        3
  •  1
  •   C. A. McCann Ravikant Cherukuri    14 年前

    将足够的逻辑放入类型类中,以支持用例所需的共享操作。创建具有所需表示形式的类型,并为该类型创建类型类的实例。然后,对于每个用例,使用newtype为其创建包装器,并派生公共类。

    首先,它迫使许多函数具有不必要的多态性——即使在实践中每个实例对不同的包装器执行相同的操作,类型类的开放世界假设意味着编译器必须考虑其他实例的可能性。虽然GHC肯定比一般的编译器更聪明,但是你能提供的信息越多,它就越能帮助你。

    第二,这会为更复杂的数据结构造成瓶颈。包装类型上的任何泛型函数都将被约束到类型类提供的接口,因此除非该接口在表达性和效率方面都是详尽的,否则您可能会遇到使用该类型的算法出现问题,或者在发现缺少的功能时重复更改类型类的风险。

    另一方面,如果包装的类型已经保持抽象(即,它不导出构造函数),那么瓶颈问题是不相关的,因此类型类可能是有意义的。否则,我可能会使用幻影类型标记(或者可能是标识) Functor sclv描述的方法)。