5

我目前正在使用 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 的初学者)都是以下两个选项,使用undefinedor Maybe

1:

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

2:

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

有没有更可取的方法来实现这一目标?谢谢!

4

2 回答 2

4

你是对的,这里有些东西不对。考虑它的最佳方法是从函数开始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它们应用于什么的函数,并且您可以编写在特定Figures 上定义的函数。

现在,如果我们在一个更高层的函数中并且有一个对 a 的引用Figure并且我们想要计算它diameter 是否是 a CircleFigure,那么你可以使用模式匹配来做到这一点。

注意:使用undefined或异常(在纯代码中)可能是代码异味。它可能可以通过重新考虑你的类型来解决。如果必须指示失败,请使用Maybe/ Either

于 2016-01-23T14:28:44.623 回答
3

您的类型定义本身(即data Figure = ...)正在引入部分函数。例如,即使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

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

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

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

类型类的一个优点是它们更容易扩展;例如,如果以后想添加一种Triangle类型,他可以选择加入任何适用于三角形的类型类,并仅为这些类型类定义一个实例。

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

>> 作为参考, haskell cafe 邮件列表上最近有类似的关于数据声明与类型类的讨论。

于 2016-01-23T14:30:56.820 回答