Главная » Haskell » Определение нескольких экземпляров для уникальной пары (класс, тип)

0

В языке Haskell запрещено создание нескольких экземпляров некоторого класса для одного и  того же  типа данных.  Действительно, в противном случае транслятор языка не смог бы  определить, какой именно экземпляр использовать при рассмотрении прикладных функций, когда такие прикладные функции работают с экземплярами некоторых классов. В стандарте языка запрещено даже  определять разные экземпляры одного и того же  класса для типов,  являющихся общим и конкретизированным вариантом одного алгебраического типа данных (например, нельзя создать экземпляры класса Eq для типов Maybe  a и Maybe  Int, поскольку более общий тип, использующий параметрический полиморфизм, перекрывает собой конкретизированный, и в этом случае транслятор также находится в затруднении). Хотя последнее ограничение  снято в некоторых компиляторах (например, GHC), оно обычно входит в состав нестандартизированных расширений языка.

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

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

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

на 10). Просто так определить новый экземпляр класса Eq для типа Int нельзя, такой экземпляр уже  существует в стандартном  модуле Prelude.  Поэтому необходимо определить изоморфный класс:

newtype Int10  = Int10 Int

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

instance Eq  Int10 where

(Int10 x) == (Int10 y) = (x ‘mod‘  10)  == (y ‘mod‘  10)

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

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

По теме:

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