Первая примечательная вещь в объявлении – это декларация типа. Она говорит, что функция принимает фигуру и возвращает значение типа Float. Мы не смогли бы записать функцию типа Circle –> Float, потому что идентификатор Circle не является типом; типом является идентификатор Shape. По той же самой причине мы не смогли бы написать функцию с типом True –> Int. Вторая примечательная вещь – мы можем выполнять сопоставление с образцом по конструкторам. Мы уже записывали подобные сопоставления раньше (притом очень часто), когда сопоставляли со значениями [], False, 5, только эти значения не имели полей. Только что мы записали конструктор и связали его поля с именами. Так как для вычисления площади нам нужен только радиус, мы не заботимся о двух первых полях, которые говорят нам, где располагается круг.

ghci> area $ Circle 10 20 10

314.15927

ghci> area $ Rectangle 0 0 100 100

10000.0

Ура, работает! Но если попытаться напечатать Circle 10 20 5 в командной строке интерпретатора, то мы получим ошибку. Пока Haskell не знает, как отобразить наш тип данных в виде строки. Вспомним, что когда мы пытаемся напечатать значение в командной строке, интерпретатор языка Haskell вызывает функцию show, для того чтобы получить строковое представление значения, и затем печатает результат в терминале. Чтобы определить для нашего типа Shape экземпляр класса Show, модифицируем его таким образом:

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

  deriving (Show)

Не будем пока концентрировать внимание на конструкции deriving (Show). Просто скажем, что если мы добавим её в конец объявления типа данных, Haskell автоматически определит экземпляр класса Show для этого типа. Теперь можно делать так:

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

Конструкторы значений – это функции, а значит, мы можем их отображать, частично применять и т. д. Если нам нужен список концентрических кругов с различными радиусами, напишем следующий код:

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]

<p>Верный способ улучшить фигуру</p>

Наш тип данных хорош, но может быть и ещё лучше. Давайте создадим вспомогательный тип данных, который определяет точку в двумерном пространстве. Затем используем его для того, чтобы сделать наши фигуры более понятными:

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))

Перейти на страницу:

Похожие книги