Главная » Haskell » Контекст  и прикладные функции

0

Всё вышеперечисленное так и не позволило ответить на самый главный вопрос: для чего нужны классы, особенно принимая  во внимание их столь абстрактную природу? Действительно, какой смысл создавать классы, если их семантика определяется вне  программы? Только ради перегрузки имён функций? Но для этого  можно было использовать другие, более понятные механизмы — например, перегрузка имён функций в языке  C не использует  никаких  классов и очень даже понятна.

Однако основное использование  классов типов в языке  Haskell  определяется системой типизации, которая используется в этом языке программирования. Читатель наверняка уже неоднократно замечал, что в сигнатурах функций используются записи, подобные таким: (Eq a)  =>, (Ord  a,  Num  b)  => и т. д. Эти директивы, как уже было сказано, называются контекстом использования переменных типов. Они обозначают, что в типе, указанном после данной директивы, переменные типов должны быть экземплярами соответствующих  классов: класса Eq или класса Ord и класса Num.

Другими словами, запись вида

gcd :: Integral a => a ->  a ->  a

означает, что переменная типа a в этой записи может являться только экземпляром класса Integral.

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

Таким образом, если в сигнатуре прикладной функции имеется контекст, то такая функция может использоваться только с соответствующими типами дан-

ных. Это позволяет создавать функции, которые  «не знают» о  природе  типов своих параметров, но, тем не менее, могут совершать над значениями таких типов определённые операции, описанные в классе. В разделе 3.3. будут приведены примеры создания прикладных функций  для значений неопределённых типов, о которых лишь известно, что они являются экземплярами определённых классов.

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

Собственно, такое же значение и при использовании контекста в определениях классов, но здесь имеется один нюанс.Рассмотрим, к  примеру, определение класса Num из стандартного модуля Prelude, определяющего типы величин, которые могут использоваться в качестве чисел, над которыми нельзя проводить операцию деления (/):

class (Eq  a, Show  a)  => Num  a where (+)             :: a  ->  a ->  a

(-)         :: a ->  a ->  a

(*)         :: a  ->  a ->  a negate           ::  a ->  a

abs                 :: a ->  a signum           :: a  ->  a fromInteger ::  Integer ->  a fromInt          :: Int  ->  a

x  y       = x  +  negate  y fromInt    =  fromIntegral negate  x  = 0  x

Как видно, в этом определении использован контекст (Eq a,  Show  a)  =>. Это значит, что использованная  параметрическая  переменная типа  a  должна обозначать такие и только такие типы данных, которые  являются экземплярами одновременно и класса Eq, и класса  Show. Это вполне понятно и следует из определения понятия «контекст». Но  здесь этот контекст распространяется на все методы класса Num.

Иногда говорят, что использование контекста при определении классов являет собой наследование классов. Действительно,  в приведённом выше примере класс Num можно понимать в качестве класса-потомка от классов Eq и Show. Правда, принимая во  внимание всё описанное в этом разделе относительно  понимания класса как ограничителя на тип данных, это не совсем то  наследование, которое рассматривается в объектно-ориентированном программировании. Но общие моменты, тем не менее, имеются.

Если рассмотреть какой-либо тип класса Num, то для него должны иметься все перечисленные методы этого класса. Но контекст в определении класса Num также обязывает этот тип быть экземплярами классов Eq и Show, а это значит, что для него должны быть определены соответствующие методы этих классов. Собственно, то же самое имеется и в объектно-ориентированном понимании наследования  — в классах-потомках  определены методы из базовых классов. Правда, обычно методы базовых классов в объектно-ориентированных языках определяются для классов-потомков автоматически, а в функциональном программировании определять экземпляры классов из контекста необходимо самостоятельно (не всегда, конечно, но об этом в разделе 3.5.).

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

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

По теме:

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