我倾向于只在函数上要求约束,这样做并没有问题。问题是,你的数据结构可能不再准确地模拟你的意图。另一方面,如果你首先将其视为数据结构,那么这就应该变得不那么重要。
我觉得我对这个问题还没有一个很好的掌握,这是一个非常模糊的问题,但我的经验法则是类型类是遵循规律(或者模拟意义)的东西,而数据类型是编码了一定数量信息的东西。
当我们想以复杂的方式层次化行为时,我发现类型类开始很吸引人,但很快就会变得痛苦起来,并且切换到字典传递可以使事情更加简单明了。也就是说,当我们希望实现具有互操作性时,我们应该回到统一的字典类型。
这是第二次尝试,扩展了一个具体的例子,但仍然只是在思考中...
假设我们想对实数建模概率分布。有两种自然表示方法。
A)基于类型类
class PDist a where
sample :: a -> Gen -> Double
B) 基于字典的
data PDist = PDist (Gen -> Double)
前者让我们做到
data NormalDist = NormalDist Double Double
instance PDist NormalDist where...
data LognormalDist = LognormalDist Double Double
instance PDist LognormalDist where...
后者让我们能够做到:
mkNormalDist :: Double -> Double -> PDist...
mkLognormalDist :: Double -> Double -> PDist...
在前者中,我们可以写
data SumDist a b = SumDist a b
instance (PDist a, PDist b) => PDist (SumDist a b)...
在后者中,我们可以简单地编写:
sumDist :: PDist -> PDist -> PDist
那么这些权衡有哪些呢?基于类型类可以让我们指定我们所拥有的分布。然而,权衡就是我们必须明确地构建一个分布代数,包括它们组合的新类型。基于数据驱动则不能限制我们所拥有的分布(甚至无论它们是否符合规范),但作为回报,我们可以随心所欲地进行操作。
此外,我们可以相对容易地编写一个
parseDist :: String -> PDist
,但我们必须经历一些痛苦才能用类型类方法实现等价物。
因此,在某种意义上,这是另一层面上的类型化/非类型化静态/动态权衡。不过我们可以加以改变,并认为类型类连同相关的代数法则指定了概率分布的语义。PDist类型也可以成为类型类PDist的一个实例。同时,我们几乎可以在任何地方都使用PDist类型(而不是类型类),并将其视为与需要使用类型类更“丰富”的实例和数据类型之间的转换关系。
事实上,我们甚至可以将基本的PDist函数定义为类型类函数的“术语”。即
mkNormalPDist m v = PDist (sample $ NormalDist m v)
。因此,在设计空间中有很多调整两个表示之间的余地...
data Partition a i = Partition { index :: a -> i, iBounded :: Bounded i, iEnum :: Enum i }
。 - Rotsordata Partition x y where Partition :: (Bounded i, Enum i) => { index :: a -> i } :: Partition a i
. (注:GADT 是 Generalized Algebraic Data Type(广义代数数据类型)的缩写,是 Haskell 的一个扩展,用于定义更精确、更灵活的数据类型。上述代码定义了一个名为Partition
的类型构造器,它有两个类型参数x
和y
,并且有一个带约束的构造函数Partition
,其中index :: a -> i
是一个函数,它将类型为a
的值映射到类型为i
的索引。) - Daniel Fischer