Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MakeevGA-Haskell-a4-shortcode_2014_05_31.doc
Скачиваний:
15
Добавлен:
19.01.2023
Размер:
1.79 Mб
Скачать

О сравнении, отображении и прочих стандартных операциях

Мы создали тип данных, Point, однако первая же попытка распечатать в интерпретаторе значение этого типа приведет к ошибке:

SomeModule> Pt 1 2

ERROR - Cannot find "show" function for:

*** Expression : Pt 1 2

*** Of type : Point

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

data Point = Pt {px :: Integer, py :: Integer}

deriving (Eq, Show)

Вот этот маленький довесок нам очень облегчит жизнь. Он позволит сравнивать значения типа Point между собой и видеть значения этого типа в интерпретаторе. Смысл этой магии в том, что мы просим интерпретатор как-нибудь самостоятельно придумать, как сравнивать и отображать значения этого типа:

SomeModule> Pt 1 2

Pt{px=1,py=2}

Параметрические типы данных

Рассмотренный тип данных, Point, представляет собой точку с целочисленными координатами. А если мы захотим нецелочисленные координаты – создавать новый тип и переписывать все функции? Для того, чтобы этого избежать, можно сразу создать параметризованные типы данных:

data Point a = Pt a a deriving (Eq, Show)

Как видим, вместо конкретного типа Integer, мы использовали какое-то a, которое в данном случае будет называться – переменная типа. По сути, под a может скрываться любой тип – и Float, и Integer, и даже Char. Что такое здесь Pt? Это бинарный конструктор данных: он берет кусочки данных и создает из них сложные данные. Что такое Point теперь? Это унарный конструктор типа: он берет какой-то тип a и создает на его основе сложный тип Point a:

Pt :: a -> a -> Point a

Pt 1 2 :: Point Integer

Pt 1.0 2.0 :: Point Float

Pt 'a' 'b' :: Point Char

Кстати, вместо Pt можно использовать то же самое слово Point. Это разрешено, потому что имена конструкторов типов и конструкторов данных находятся в разных пространствах имен. Короче говоря, интерпретатор всегда поймет, о чем идет речь – о конструкторе типа или о конструкторе данных, поэтому они могут иметь одинаковые имена.

Кстати, а что теперь с нашей функцией квадрата расстояния:

sqdist :: Num a => Point a -> Point a -> a

sqdist (Pt x1 y1) (Pt x2 y2) = ((x1-x2)^2 + (y1-y2)^2)

Вот какой у нее теперь оказался тип: она будет работать только с точками, у которых координаты имеют численный тип. Что логично, раз функция эти координаты вычитает и возводит в квадрат.

Сложные типы данных

Теперь сведя в кучу все рассмотренные возможности, мы создадим новый тип данных – фигура, которая может быть точкой, а может быть и кругом:

data Shape =

Point {x :: Float, y :: Float} |

Circle {x :: Float, y :: Float, r :: Float}

deriving (Eq, Show)

О чем говорит эта запись? О том, что значение нового типа данных Shape может быть или точкой, и тогда внутри нее будут храниться только координаты, или кругом, и тогда внутри будут храниться координаты и радиус:

Point 1 1 :: Shape

Circle 1 1 2 :: Shape

Раз мы задали имена полям, то автоматически создано несколько функций для доступа к ним. Причем для всех полей – хотя, конечно, вызов функции r от точки приведет к ошибке.

x :: Shape -> Float

y :: Shape -> Float

r :: Shape -> Float

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

Сейчас у нас возможных значений (возможных точек и кругов) бесконечное число, но все они – или точки, или круги. Поэтому нам необходимо и достаточно описать, как требуемая функция будет работать в случае каждого из двух возможных способов организации значений Shape. То бишь, проще говоря – как вычислять площадь точек, а как площадь кругов:

square :: Shape -> Float

square (Point _ _) = 0

square (Circle _ _ r) = 3.14 * r^2

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