Главная » Haskell » Экземпляр  — связь между  типом и классом

0

Система типизации в языке  Haskell предоставляет  поистине  удивительную технологию, которая позволяет создавать дополнения к интерфейсам (наборам функций, оперирующих с заданным типом данных) «на лету». Эта технология основывается на понятии экземпляра класса.

Экземпляры классов очень важны, поскольку отвечают за связь классов и типов, что, в конечном итоге, ведёт к тем свойствам системы типизации, реализованной в языке Haskell, которые были кратко описаны в предыдущих разделах.

Если  рассматривать объектную  модель программных  сущностей   языка Haskell, то в ней экземпляры классов будут представлены связью (или параметризованной связью) между классами и типами. Действительно, много уже было сказано про то, что классы типов и  типы данных связываются друг с другом для того, чтобы определить для значений заданного типа определённое классом поведение (методы, описываемые классом). Собственно, экземпляр класса и есть такая связь.

В языке Haskell экземпляр класса определяется практически так же, как и сам класс, за исключением того, что для этого используется ключевое слово instance, а вместо сигнатур методов после декларации экземпляра приводятся их реализации для конкретного типа. Например, следующим образом в стандартном модуле Prelude определён экземпляр класса Eq (этот класс определяет типы сравнимых величин) для списков:

instance Eq a => Eq  [a] where []     ==  []     =   True

(x:xs) == (y:ys) =   x  == y  &&  xs  == ys

_           == _           =   False

Как  видно, в определении экземпляров также можно использовать контекст. И действительно, сравнивать списки значений можно тогда и только тогда, когда можно сравнивать сами значения, заключённые в списки. Об этом и говорит контекст в определении этого экземпляра. Собственно, этот пример и показывает, как необходимо определять экземпляры. После ключевого слова instance записывается наименование класса (после контекста, если он необходим),  затем — наименование типа. В этом случае данный тип заменит собой параметрическую  переменную типа в декларации класса, и все методы класса получат определённый тип, зависящий от типа, для которого определяется экземпляр. То есть если у операции (==) при объявлении внутри класса Eq был заявлен тип a ->  a ->  Bool, то при определении  экземпляра для типа [] произойдёт конкретизация  параметрической переменной, в связи с чем операция (==) для типа [] будет иметь тип [a] ->  [a] ->  Bool (здесь, конечно, имеется коллизия имён параметрических переменных, но читать этот пример необходимо так, как будто бы в декларации класса и его экземпляра используются разные переменные a).

После имени типа в определении экземпляра записывается ключевое  слово

where, после которого уже следует перечисление реализаций методов для кон-

кретного типа данных. Тут  необходимо  отметить, что  реализовывать  можно только те методы, которые необходимы (обычно это особо оговаривается  в комментариях к определению класса). Те методы, которые имеют выражение  через другие методы  и функции по умолчанию, можно не определять   — транслятор языка Haskell построит их самостоятельно.

В приведённом выше примере как раз не реализуется операция (/=)  для типа []. Транслятор построит её самостоятельно,  взяв за основу определение этой операции из класса:

x  /=  y  =  not  (x ==  y)

Тут  видно, что просто инвертируется результат выполнения  операции (/=),  поэтому для списков полное определение этой операции будет сгенерировано следующим образом:

(/=) :: Eq a => [a] ->  [a] ->  Bool xs  /=  yx  =  not  (xs  == ys)

Хотя, конечно же, можно было бы определить эту операцию подобно операции (==):

(/=) :: Eq a => [a] ->  [a] ->  Bool []     /=  []          =   False

(x:xs) /=  (y:ys) =   x  /=  y  || xs  /=  ys

_           == _           =    True

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

Остаётся упомянуть,  что  сама программная сущность  «экземпляр класса» в языке  Haskell безымянна, а потому для  произвольной уникальной пары (класс, тип) можно определить только один экземпляр. После дальнейшего примера будет рассказано про то, как обойти это ограничение.

Источник: Душкин Р. В., Справочник по языку Haskell. М.: ДМК Пресс, 2008. 544 с., ил.

По теме:

  • Комментарии