import qualified Geometry.Cube as Cube
Затем мы сможем вызывать функции Sphere.area
, Sphere.volume
, Cuboid.area
и т. д., и каждая функция вычислит площадь или объём соответствующего объекта.
В следующий раз, когда вы поймаете себя за написанием огромного файла с кучей функций, попытайтесь выяснить, какие функции служат некоей общей цели, и можно ли включить их в отдельный модуль.
Позднее при написании программы со схожей функциональностью вы сможете просто импортировать свой модуль.
7
Создание новых типов и классов типов
В предыдущих главах мы изучили некоторые типы и классы типов в языке Haskell. Из этой главы вы узнаете, как создать и заставить работать свои собственные!
Введение в алгебраические типы данных
До сих пор мы сталкивались со многими типами данных – Bool
, Int
, Char
, Maybe
и др. Но как создать свой собственный тип? Один из способов – использовать ключевое слово data
. Давайте посмотрим, как в стандартной библиотеке определён тип Bool
:
data Bool = False | True
Ключевое слово data
объявляет новый тип данных. Часть до знака равенства вводит идентификатор типа, в данном случае Bool
. Часть после знака равенства – это конструкторы данных, которые также называют конструкторами значений. Они определяют, какие значения может принимать тип. Символ |
означает «или». Объявление можно прочесть так: тип Bool
может принимать значения True
или False
. И имя типа, и конструкторы данных должны начинаться с прописной буквы.
Рассуждая подобным образом, мы можем думать, что тип Int
объявлен так:
data Int = –2147483648 | –2147483647 | ... | –1 | 0 | 1 | 2 | ... | 2147483647
Первое и последнее значения – минимальное и максимальное для Int
. На самом деле тип Int
объявлен иначе – видите, я пропустил уйму чисел – такая запись полезна лишь в иллюстративных целях.
Отличная фигура за 15 минут
Теперь подумаем, как бы мы представили некую геометрическую фигуру в языке Haskell. Один из способов – использовать кортежи. Круг может быть представлен как (43.1, 55.0, 10.4)
, где первое и второе поле – координаты центра, а третье – радиус. Вроде бы подходит, но такой же кортеж может представлять вектор в трёхмерном пространстве или что-нибудь ещё. Лучше было бы определить свой собственный тип для фигуры. Скажем, наша фигура может быть кругом или прямоугольником.
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
Ну и что это? Размышляйте следующим образом. Конструктор для значения Circle
содержит три поля типа Float
. Когда мы записываем конструктор значения типа, опционально мы можем добавлять типы после имени конструктора; эти типы определяют, какие значения будет содержать тип с данным конструктором. В нашем случае первые два числа – это координаты центра, третье число – радиус. Конструктор для значения Rectangle
имеет четыре поля, которые также являются числами с плавающей точкой. Первые два числа – это координаты верхнего левого угла, вторые два числа – координаты нижнего правого угла.
Когда я говорю «поля», то подразумеваю «параметры». Конструкторы данных на самом деле являются функциями, только эти функции возвращают значения типа данных. Давайте посмотрим на сигнатуры для наших двух конструкторов:
ghci> :t Circle
Circle :: Float –> Float –> Float –> Shape
ghci> :t Rectangle
Rectangle :: Float –> Float –> Float –> Float –> Shape
Классно, конструкторы значений – такие же функции, как любые другие! Кто бы мог подумать!..
Давайте напишем функцию, которая принимает фигуру и возвращает площадь её поверхности:
area :: Shape –> Float
area (Circle _ _ r) = pi * r ^ 2
area (Rectangle x1 y1 x2 y2) = (abs $ x2 – x1) * (abs $ y2 – y1)