Верный способ улучшить фигуру
Наш тип данных хорош, но может быть и ещё лучше. Давайте создадим вспомогательный тип данных, который определяет точку в двумерном пространстве. Затем используем его для того, чтобы сделать наши фигуры более понятными:
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
Обратите внимание, что при определении точки мы использовали одинаковые имена для конструктора типа и для конструктора данных. В этом нет какого-то особого смысла, но если у типа данных только один конструктор, как правило, он носит то же имя, что и тип. Итак, теперь у конструктора Circle два поля: первое имеет тип Point, второе – Float. Так легче разобраться, что есть что. То же верно и для прямоугольника. Теперь, после всех изменений, мы должны исправить функцию area:
area :: Shape –> Float
area (Circle _ r) = pi * r 2
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 – x1) * (abs $ y2 – y1)
Единственное, что мы должны поменять, – это образцы. Мы игнорируем точку у образца для круга. В образце для прямоугольника используем вложенные образцы при сопоставлении для того, чтобы получить все поля точек. Если бы нам нужны были точки целиком, мы бы использовали именованные образцы. Проверим улучшенную версию:
ghci> area (Rectangle (Point 0 0) (Point 100 100))
10000.0
ghci> area (Circle (Point 0 0) 24)
1809.5574
Как насчёт функции, которая двигает фигуру? Она принимает фигуру, приращение координаты по оси абсцисс, приращение координаты по оси ординат – и возвращает новую фигуру, которая имеет те же размеры, но располагается в другом месте.
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))
Всё довольно очевидно. Мы добавляем смещение к точкам, определяющим положение фигуры:
ghci> nudge (Circle (Point 34 34) 10) 5 10
Circle (Point 39.0 44.0) 10.0
Если мы не хотим иметь дело напрямую с точками, то можем сделать вспомогательные функции, которые создают фигуры некоторого размера с нулевыми координатами, а затем их подвигать.
Во-первых, напишем функцию, принимающую радиус и создающую круг с указанным радиусом, расположенный в начале координат:
baseCircle :: Float –> Shape
baseCircle r = Circle (Point 0 0) r
Добавим функцию, которая по заданным ширине и высоте создаёт прямоугольник соответствующего размера. При этом левый нижний угол прямоугольника находится в начале координат:
baseRect :: Float –> Float –> Shape
baseRect width height = Rectangle (Point 0 0) (Point width height)
Теперь создавать формы гораздо легче: достаточно создать форму в начале координат, а затем сдвинуть её в нужное место:
ghci> nudge (baseRect 40 100) 60 23
Rectangle (Point 60.0 23.0) (Point 100.0 123.0)