In “Making Our Own Types and Typeclasses” they give the following piece of code :
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)
As it stands the pattern matching against constructors is getting quite cluttered at the point
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = ....
Had we defined the Shape type as :
data Shape = Circle Point Float | Rectangle Float Float Float Float deriving (Show)
Then even though we lose a bit of clarity into the nature of the type, the patten matching looks less cluttered, as can be seen below :
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)
My question is whether it is possible to have both
Rectangle Point Point
and
Rectangle Float Float Float Float
in the same piece of code (i.e. a sort of “overloading” of value constructors), so that we can do something like :
...
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)
where the “…” denotes the same as in the code above. Also are there any other tricks to make the notation a bit more compact at the “nudge (Rectangle….” point? Thanks
You could possibly use type classes to make a function behave as both
Point -> Point -> RectangleandFloat -> Float -> Float -> Float -> Rectangle, but I wouldn’t advocate it. It will be to much trouble for the gain. I don’t think there’s anyway you could make such an overloaded name usable in pattern matching anyway.The way I see it, if you’re only ever going to be using
Pointvalues by deconstructing them and operating on the rawFloatvalues, then you’re not really getting that much out of it, so you could resolve your problem by getting rid of it entirely.But you’re missing a golden opportunity to implement a function to adjust a point directly!
For starters I would make an
Offsettype to hold youraandbvalues. Then you make a functionadjust :: Offset -> Point -> Pointto do the combining. And then yournudgedoesn’t even need to understand the internal structure of aPointto do its job!For example (Disclaimer: I haven’t actually compiled this)1:
And similarly there could be a whole family of operations on
PointandOffset. For exampleoffsetFrom :: Point -> Point -> Offsetcould be useful in yoursurfacefunction. I once went overboard and used type classes to implement a family of operators (|+|,|*|, etc IIRC) which allowed various things to be combined (for example, you can add aPointand anOffsetin either order to get aPoint, you can add and subtractOffsets but notPoints, you can multiplyOffsets by scalars but notPoints, etc). Not sure whether it was worth it in the end, but it made my code look like my maths a little more!With your current code you’re effectively implementing all operations on
Pointagain every time you need them (including the same adjustment operation twice in the same equation innudge, which is my take on why it looks quite so bad).1 There’s a certain argument to be made for making functions like
adjustandnudgehave signatures where the “main” thing being operated on comes last, soadjust :: Offset -> Point -> Pointandnudge :: Offset -> Shape -> Shape. This can come in handy because then partially applyingadjustgives you a “point transformer” with typePoint -> Point, and similarly you can partially applynudgeto get a “shape transformer” with typeShape -> Shape.This helps when you have a collection of points or shapes and you want to apply the same transformation to all of them, for example:
And generally “transformers” with type
Something -> Somethingare just useful things to have on your main data structures. So whenever you have a function that combines some auxiliary data with aSomethingto produce a newSomething, it’ll often turn out to be useful to put theSomethingas the last argument, so you have another easy source of transformer functions.