处理具有相同内部表示和最小样板的多种类型?

Car*_*arl 11 haskell

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

有两种相对明显的方法可以解决这个问题.

一个是使用类型类和GeneralizedNewtypeDeriving扩展名.将足够的逻辑放入类型类中以支持用例所需的共享操作.创建具有所需表示的类型,并为该类型创建类型类的实例.然后,对于每个用例,使用newtype为它创建包装器,并派生公共类.

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

我主要关心的不是混合共享内部表示和操作的值,而是在我的代码中有不同的含义.这两种方法都解决了这个问题,但感觉非常笨拙.我的第二个问题是减少所需的样板量,两种方法都做得很好.

每种方法的优点和缺点是什么?是否有一种技术更接近于我想做的事情,提供没有样板代码的类型安全?

Ant*_*ony 2

我对玩具示例进行了基准测试,没有发现这两种方法之间存在性能差异,但用法通常略有不同。

例如,在某些情况下,您有一个泛型类型,其构造函数是公开的,并且您希望使用newtype包装器来指示语义上更具体的类型。然后使用newtypes 会导致调用站点,例如,

s1 = Specific1 $ General "Bob" 23
s2 = Specific2 $ General "Joe" 19
Run Code Online (Sandbox Code Playgroud)

不同特定新类型之间的内部表示相同的事实是透明的。

类型标签方法几乎总是与表示构造函数隐藏一起出现,

data General2 a = General2 String Int
Run Code Online (Sandbox Code Playgroud)

以及智能构造函数的使用,导致数据类型定义和调用站点,例如,

mkSpecific1 "Bob" 23
Run Code Online (Sandbox Code Playgroud)

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

myValue = General2 String Int :: General2 Specific1
Run Code Online (Sandbox Code Playgroud)

一旦采用智能构造函数,您就可以轻松添加额外的验证逻辑来​​捕获标签的误用。幻像类型方法的一个很好的方面是,对于有权访问表示的内部代码来说,模式匹配根本不会改变。

internalFun :: General2 a -> General2 a -> Int
internalFun (General2 _ age1) (General2 _ age2) = age1 + age2
Run Code Online (Sandbox Code Playgroud)

当然,您可以将newtypes 与智能构造函数和内部类一起使用来访问共享表示,但我认为此设计空间中的一个关键决策点是您是否希望保持表示构造函数的公开。如果表示的共享应该是透明的,并且客户端代码应该可以自由地使用它希望的任何标签而无需额外的验证,那么newtype包装器就可以GeneralizedNewtypeDeriving正常工作。但是,如果您打算采用智能构造函数来处理不透明的表示,那么我通常更喜欢幻像类型。