3

“制作我们自己的类型和类型类”中,他们给出了以下代码:

data Point = Point Float Float deriving (Show)  
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

surface :: Shape -> Float  
surface (Circle _ r) = pi * r ^ 2  
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)  

nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r  
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1+a) (y1+b)) (Point (x2+a) (y2+b))

main = do
    print (surface (Circle (Point 0 0) 24))
    print (nudge (Circle (Point 34 34) 10) 5 10)

就目前而言,针对构造函数的模式匹配在这一点上变得非常混乱

nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = ....

如果我们将 Shape 类型定义为:

data Shape = Circle Point Float | Rectangle Float Float Float Float deriving (Show)

然后,即使我们对类型的性质失去了一点清晰,模式匹配看起来也不那么混乱,如下所示:

data Point = Point Float Float deriving (Show)  
data Shape = Circle Point Float | Rectangle Float Float Float Float deriving (Show)

surface :: Shape -> Float  
surface (Circle _ r) = pi * r ^ 2  
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)  

nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r  
nudge (Rectangle x1 y1 x2 y2) a b = Rectangle (x1+a) (y1+b) (x2+a) (y2+b)

main = do
    print (surface (Circle (Point 0 0) 24))
    print (nudge (Circle (Point 34 34) 10) 5 10)

我的问题是是否可以同时拥有

Rectangle Point Point

Rectangle Float Float Float Float

在同一段代码中(即一种“重载”值构造函数),以便我们可以执行以下操作:

...
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)  

...
nudge (Rectangle x1 y1 x2 y2) a b = Rectangle (x1+a) (y1+b) (x2+a) (y2+b)

其中“...”表示与上面代码中的相同。还有其他技巧可以使符号在“轻推(矩形......”点上更紧凑吗?谢谢

4

3 回答 3

4

一种选择是使用视图模式。让我给你一个简短的例子:

{-# LANGUAGE ViewPatterns #-}

data Point = Point Float Float
data Shape = Circle Point Float | Rectangle Point Point

rectangleAsPoints :: Shape -> Maybe (Point,Point)
rectangleAsPoints (Rectangle a b) = Just (a,b)
rectangleAsPoints _               = Nothing

rectangleFromPoints :: Point -> Point -> Shape
rectangleFromPoints = Rectangle

rectangleAsCoords :: Shape -> Maybe (Float,Float,Float,Float)
rectangleAsCoords (Rectangle (Point x y) (Point a b)) = Just (x,y,a,b)
rectangleAsCoords _                                   = Nothing

rectangleFromCoords :: Float -> Float -> Float -> Float -> Shape
rectangleFromCoords a b c d = Rectangle (Point a b) (Point c d)

surface (rectangleAsPoints -> Just (Point x1 y1, Point x2 y2)) =
  (abs $ x2 - x1) * (abs $ y2 - y1)
surface (Circle _ r) = pi * r ^ 2

nudge (rectangleAsCoords -> Just (x1,y1,x2,y2)) a b =
  rectangleFromCoords (x1+a) (y1+b) (x2+a) (y2+b)
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r

为了一致性起见,我将矩形的两个视图都实现为函数。这样, Rectangle 类型的实际实现可以保持隐藏。

请注意如何混合正常模式匹配和视图模式。

于 2012-11-11T21:27:01.170 回答
4

您可以使用类型类来使函数同时表现为Point -> Point -> Rectangleand Float -> Float -> Float -> Float -> Rectangle,但我不提倡这样做。为了收益会很麻烦。我不认为无论如何你都可以使这样一个重载的名称在模式匹配中可用。

在我看来,如果你只是Point通过解构它们并操作原始Float值来使用值,那么你并没有真正从中得到那么多,所以你可以通过摆脱来解决你的问题它完全。

但是您错过了实现直接调整点的功能的绝佳机会!

对于初学者,我会制作一种Offset类型来保存您的ab价值观。然后你制作一个函数adjust :: Offset -> Point -> Point来进行组合。然后你nudge甚至不需要了解 a 的内部结构Point来完成它的工作!

例如(免责声明:我实际上并没有编译这个)1

data Point = Point Float Float deriving (Show)
data Offset = Offset Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

adjust :: Point -> Offset -> Point
adjust (Point x y) (Offset ox oy) = Point (x + ox) (y + oy)

nudge :: Shape -> Offset -> Shape
nudge (Circle c r) o = Circle (adjust c o) r  
nudge (Rectangle p1 p2) o = Rectangle (adjust p1 o) (adjust p2 o)

Point同样,在和上可能存在一整套操作Offset。例如offsetFrom :: Point -> Point -> Offset可能对您的surface功能有用。我曾经过分使用类型类来实现一系列运算符(|+|,|*|等 IIRC),它允许组合各种事物(例如,您可以添加 aPoint和 anOffset以得到 a Point,您可以添加和减去Offsets但不是Points,您可以将Offsets 乘以标量但不能乘以 sPoint等)。不确定最终是否值得,但它让我的代码看起来更像我的数学!

使用您当前的代码,您Point每次需要它们时都有效地再次执行所有操作(包括在 中的同一个等式中两次相同的调整操作nudge,这就是我对它看起来如此糟糕的原因的看法)。


1有一定的论据可以使函数像adjustnudge具有签名,其中被操作的“主要”事物排在最后,所以adjust :: Offset -> Point -> Pointnudge :: Offset -> Shape -> Shape。这可以派上用场,因为然后部分应用adjust会为您提供一个带有 type 的“点转换器” Point -> Point,同样您可以部分应用nudge来获得一个带有 type 的“形状转换器” Shape -> Shape

当您拥有一组点或形状并且想要对所有点或形状应用相同的变换时,这会有所帮助,例如:

data Shape = Polygon [Point]

adjust :: Offset -> Point -> Point
adjust (Offset ox oy) (Point x y) = Point (x + ox) (y + oy)

nudge :: Offset -> Shape -> Shape
nudge o (Polygon ps) = Polygon (map (adjust o) ps)

通常,具有类型的“转换器”Something -> Something只是对您的主要数据结构有用的东西。因此,每当您有一个将一些辅助数据与 a 组合Something以生成新的函数时,Something将 the 作为最后一个参数通常会很有用Something,因此您有另一个简单的转换器函数来源。

于 2012-11-11T21:36:13.303 回答
2

你想要的不可能。出于模式匹配的目的,您可以将ViewPatterns其用作多个构造函数的穷人替代品,并制作一个函数来简化构造:

{-# LANGUAGE ViewPatterns #-}

-- Your pattern match aid
unRectangle :: Shape -> Maybe (Float, Float, Float, Float)
unRectangle (Rectangle (Point x1 y1) (Point x2 y2)) = Just (x1,y1,x2,y2)
unRectangle _ = Nothing

-- your construction aid
rectangle :: Float -> Float -> Float -> Float -> Shape
rectangle x y u v = Rectangle (Point x y) (Point u v)

surface (unRectangle -> Just (x1,y1,x2,y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
...
nudge (unRectangle -> Just (x1,y1,x2,y2)) = rectangle (x1+a) (y1+b) (x2+a) (y2+b)
于 2012-11-11T21:35:50.687 回答