基本问题:在使用类或使用记录(具有多态字段)之间进行选择时,应遵循哪些设计原则?
首先,我们知道类和记录在本质上是等效的(因为在Core中,类成为字典的字典,而字典只是记录)。尽管如此,还是有区别的:类是隐式传递的,记录必须是显式的。
再深入一点,在以下情况下,类确实很有用:
当我们只有(仅参数多态性)数据的一种表示形式时,类很尴尬,但是我们有多个实例。如果我们不想打开所有麻烦的扩展名,这会导致不得不使用 newtype 来添加额外的标签(仅存在于我们的代码中,因为我们知道这样的标签在运行时会被删除),从而产生语法噪音。 (即重叠和/或不确定的实例)。
当然,事情变得更加困惑:如果我想限制自己的类型怎么办?我们来看一个真实的例子:
class (Bounded i, Enum i) => Partition a i where
index :: a -> i
我可以轻松完成
data Partition a i = Partition { index :: a -> i}
但是现在我已经失去了约束,而必须将它们添加到特定的函数中。
有设计准则对我有帮助吗?
最佳答案
我倾向于只需要对功能进行约束就不会出现问题。我想问题是,您的数据结构不再精确地按照您的意图建模。另一方面,如果您首先将其视为数据结构,则该问题就不那么重要了。
我觉得我不一定仍然对这个问题有很好的了解,这大概是模糊的,但是我的经验法则往往是类型类是遵守法律(或模型含义)的事物,而数据类型是数据类型是编码一定数量信息的事物。
当我们想以复杂的方式对行为进行分层时,我发现类型类开始时很诱人,但很快就会变得痛苦起来,切换到字典传递使事情变得更加简单。这就是说,当我们希望实现可以互操作时,我们应该退回到统一的字典类型。
这需要两个时间,在一个具体示例上进行了一些扩展,但是仍然只是一些旋转的想法...
假设我们要对实数上的概率分布建模。我想到了两种自然的表述。
A)类型类驱动
class PDist a where
sample :: a -> Gen -> Double
B)字典驱动
data PDist = PDist (Gen -> Double)
前者让我们做
data NormalDist = NormalDist Double Double -- mean, var
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)
因此设计空间中有很多空间可以根据需要在两个表示之间滑动...关于haskell - 在类(class)和记录之间选择,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8106764/