【问题标题】:How to make function useable only for a certain data constructor of an ADT?如何使函数仅可用于 ADT 的某个数据构造函数?
【发布时间】:2016-04-30 01:48:04
【问题描述】:

我目前正在使用 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

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

【问题讨论】:

    标签: haskell pattern-matching algebraic-data-types maybe undefined-function


    【解决方案1】:

    你说得对,这里有些东西不对劲。考虑它的最佳方式是从函数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 应用于什么的函数,并且您可以编写在特定 @987654326 上定义的函数@s.

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

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

    【讨论】:

    • 由于xy字段标签的多个定义,这将无法编译(直到DuplicateRecordFields被登陆)
    • 另一种选择是使用data PlacedFigure = PlacedFigure { x :: Integer, y :: Integer, figure :: Figure }。这提取了图形放置的共同特征。也许效率不高,但通常没问题。
    • 是的,更通用的是data WithPlacement a = WithPlacement { x :: Integer, y :: Integer, placed :: a }
    【解决方案2】:

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

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

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

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

    class Figure a where
        area, perimeter :: a -> Double
    
    instance Figure Circle where
        area = ...
        perimeter = ...
    

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

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

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

    >> 供参考,haskell cafe mailing list 最近也有类似data declaration vs type classes 的讨论。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-17
      • 2012-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-06
      • 2018-02-20
      • 2012-06-17
      相关资源
      最近更新 更多