基本问题:在选择使用类或使用记录(具有多态字段)时,应该遵循哪些设计原则?
首先,我们知道类和记录本质上是等价的(因为在Core中,类被人们看作字典,这只是记录)。然而,存在差异:类是隐式传递的,记录必须是显式的。
更深入地看,课程在以下情况下非常有用:
当我们(仅参数多态)只有一个数据表示时,类很尴尬,但我们有多个实例。如果我们不想打开,这会导致必须使用 newtype 添加额外标记(仅存在于我们的代码中,因为我们知道这些标记会在运行时被删除)的语法噪音各种麻烦的扩展(即重叠和/或不可判断的实例)。
当然,事情变得更加混乱:如果我想对我的类型有限制怎么办?让我们选一个真实的例子:
class (Bounded i, Enum i) => Partition a i where
index :: a -> i
我可以轻松完成
data Partition a i = Partition { index :: a -> i}
但是现在我已经失去了约束,我将不得不将它们添加到特定的功能中。
是否有设计指南可以帮助我?
答案 0 :(得分:6)
我倾向于看到只需要对函数进行约束的问题。我想问题是,您的数据结构不再精确地模拟您的意图。另一方面,如果您首先将其视为数据结构,那么这应该更少。
我觉得我不一定能很好地掌握这个问题,而且这个问题虽然模糊不清,但我的经验法则往往是类词类是遵守法律(或模型意义)的东西。和数据类型是编码一定数量信息的东西。
当我们想要以复杂的方式对行为进行分层时,我发现类型类开始引人入胜,但很快就会变得痛苦,而切换到字典传递会使事情变得更加简单。也就是说,当我们希望实现可以互操作时,我们应该回归到统一的字典类型。
这需要两个,在一个具体的例子上扩展一点,但仍然只是一些旋转的想法......
假设我们想要在实数上模拟概率分布。脑海中浮现出两种自然的表现形式。
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类型(而不是类型类),而将认为作为实例塔和数据类型的iso,以便更加“丰富”地使用类型类。 / p>
实际上,我们甚至可以在类型类函数的术语中定义基本的PDist函数。即mkNormalPDist m v = PDist (sample $ NormalDist m v)
因此,设计空间中有很多空间可以在两个表示之间滑动......
答案 1 :(得分:4)
注意:我不确定我是否完全理解OP。建议/意见改进赞赏!
<强>背景强>
当我第一次了解Haskell中的类型类时,我提到的一般经验法则是,与类似Java的语言相比:
data
与类Here's另一个SO问答,描述了使用接口的指南(也是接口过度使用的一些缺点)。我的解释是:
我打赌你已经知道了这一切。
我尝试遵循自己的代码的指南是:
所以在实践中这意味着:
示例:强>
类型Show
,函数show :: (Show s) => s -> String
:用于可以表示为String
的数据。
答案 2 :(得分:1)
类型类有时可以提供额外的类型安全性(例如Ord
和Data.Map.union
。如果你有类似的情况选择类型类可能有助于你的类型安全 - 那么使用类型类。
我将提供一个不同的例子,我认为类型类不会提供额外的安全性:
class Drawing a where
drawAsHtml :: a -> Html
drawOpenGL :: a -> IO ()
exampleFunctionA :: Drawing a => a -> a -> Something
exampleFunctionB :: (Drawing a, Drawing b) => a -> b -> Something
exampleFunctionA
无法做到,exampleFunctionB
无法做到(我觉得很难解释原因,欢迎见解)。
在这种情况下,我认为使用类型类没有任何好处。
(根据Jacques的反馈和misso的问题编辑)