Learn You a Haskell for Great Good! - Miran Lipovaca [49]
So now we can do this:
ghci> Circle 10 20 5
Circle 10.0 20.0 5.0
ghci> Rectangle 50 230 60 90
Rectangle 50.0 230.0 60.0 90.0
Value constructors are functions, so we can map them, partially apply them, and so on. If we want a list of concentric circles with different radii, we can do this:
ghci> map (Circle 10 20) [4,5,6,6]
[Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0
20.0 6.0]
Improving Shape with the Point Data Type
Our data type is good, but it could be better. Let’s make an intermediate data type that defines a point in two-dimensional space. Then we can use that to make our shapes more understandable.
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
Notice that when defining a point, we used the same name for the data type and the value constructor. This has no special meaning, although it’s common if there’s only one value constructor. So now the Circle has two fields: One is of type Point and the other of type Float. This makes it easier to understand what’s what. The same goes for Rectangle. Now we need to adjust our area function to reflect these changes.
area :: Shape -> Float
area (Circle _ r) = pi * r ^ 2
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
The only thing we needed to change were the patterns. We disregarded the whole point in the Circle pattern. In the Rectangle pattern, we just used nested pattern matching to get the fields of the points. If we wanted to reference the points themselves for some reason, we could have used as-patterns.
Now we can test our improved version:
ghci> area (Rectangle (Point 0 0) (Point 100 100))
10000.0
ghci> area (Circle (Point 0 0) 24)
1809.5574
How about a function that nudges a shape? It takes a shape, the amount to move it on the x axis, and the amount to move it on the y axis. It returns a new shape that has the same dimensions but is located somewhere else.
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))
This is pretty straightforward. We add the nudge amounts to the points that denote the position of the shape. Let’s test it:
ghci> nudge (Circle (Point 34 34) 10) 5 10
Circle (Point 39.0 44.0) 10.0
If we don’t want to deal with points directly, we can make some auxiliary functions that create shapes of some size at the zero coordinates and then nudge those.
First, let’s make a function that takes a radius and makes a circle that is located at the origin of the coordinate system, with the radius we supplied:
baseCircle :: Float -> Shape
baseCircle r = Circle (Point 0 0) r
Now let’s make a function that takes a width and a height and makes a rectangle with those dimensions and its bottom-left corner located at the origin:
baseRect :: Float -> Float -> Shape
baseRect width height = Rectangle (Point 0 0) (Point width height)
Now we can use these functions to make shapes that are located at the origin of the coordinate system and then nudge them to where we want them to be, which makes it easier to create shapes:
ghci> nudge (baseRect 40 100) 60 23
Rectangle (Point 60.0 23.0) (Point 100.0 123.0)
Exporting Our Shapes in a Module
You can also export your data types in your custom modules. To do that, just write your type along with the functions you are exporting, and then add some parentheses that specify the value constructors that you want to export, separated by commas. If you want to export all the value constructors for a given type, just write two dots (..).
Suppose we want to export our shape functions