如何使函数仅可用于 ADT 的某个数据构造函数?

How to make function useable only for a certain data constructor of an ADT?

我目前正在 Haskell 中使用 ADT 并尝试构建一个 ADT Figure:

data Figure = Rect { x :: Integer, y :: Integer, width :: Integer, height :: Integer}
            | Circle { x :: Integer, y :: Integer, radius :: Integer}
            | CombiFigure Figure Figure
            deriving (Eq, Show, Read)

现在我遇到了如何实现一个不应该接受每个 Figure 的函数的问题,但是例如只有一个 Circle.

我的设计已经很糟糕了吗?或者是否有一些最佳实践如何做到这一点?

例如,考虑一个直径函数。我想到的(我是 Haskell 的完全初学者)是以下两个选项,使用 undefinedMaybe:

1:

diameter :: Figure -> Integer
diameter (Circle _ _ r) = 2 * r
diameter _ = undefined

2:

diameter :: Figure -> Maybe Integer
diameter (Circle _ _ r) = Just (2 * r)
diameter _ = Nothing

是否有更好的方法来实现这一点? 谢谢!

你是对的,这里有些不对劲。考虑它的最佳方式是从函数 diameter 开始,然后决定它的理想类型。你可能会想出

diameter :: Circle -> Integer
diameter (Circle _ _ r) = 2 * r

因为直径仅为圆定义。

这意味着您将不得不通过拆分 Circle(以及 Rect)来扩充您的数据结构:

data Figure = RectFigure Rect
            | CircleFigure Circle
            | CombiFigure Figure Figure
            deriving (Eq, Show, Read)

data Rect = Rect { rectX :: Integer, rectY :: Integer, rectWidth :: Integer, height :: Integer}
          deriving (Eq, Show, Read)

data Circle = Circle { circleX :: Integer, circleY :: Integer, circleRadius :: Integer}
            deriving (Eq, Show, Read)

这很好,因为它现在更加灵活:您可以编写不关心 Figure 它们应用到什么的函数, 您可以编写函数在特定 Figure 上定义的。

现在,如果我们在一个 higher-up 函数中并且引用了一个 Figure 并且我们想要计算它的 diameter if 是一个CircleFigure,那么你可以使用模式匹配来做到这一点。

注意:使用 undefined 或异常(在纯代码中)可能是一种代码味道。它可能可以通过重新考虑您的类型来解决。如果一定要表示失败,那就用Maybe/Either.

您的类型定义本身(即 data Figure = ...)正在引入 partial functions。例如即使 width 属于 width :: Figure -> Integer 类型,它也只能对 Rect 值起作用:

\> width $ Rect 1 2 3 4
3
\> width $ Circle 1 2 3 
*** Exception: No match in record selector width

因此,您已经定义了可以在一个图形上运行但不能在另一个图形上运行的函数(类似于问题中的 diameter 函数)。

也就是说,第三种解决方案是将 CircleRectangle 等定义为单独的类型;然后,定义一个Figure type class,它定义了这些类型的公共接口

class Figure a where
    area, perimeter :: a -> Double

instance Figure Circle where
    area = ...
    perimeter = ...

此外,每种类型都可能有自己独有的功能。或者,您可以添加更多接口,(即类型 classes)涵盖一些但不是所有图形类型。

classes 类型的一个优点是它们更容易扩展;例如如果稍后想添加一个 Triangle 类型,他可以 opt-in 任何适用于三角形的类型 class,并定义仅适用于 classes.

类型的实例

而在 data Figure = ... 方法中,您需要找到每个可以将 Figure 作为参数的函数,并确保它也能处理 Triangle。如果您要运送图书馆,那么您将无法访问所有这些功能。

>> 作为参考,最近在 haskell 网吧邮件列表上对 data declaration vs type classes 进行了类似的讨论。