По определению классы типов — это конструкция системы типов, которая поддерживает полиморфизм ad hoc.
Давайте уточним это определение, объяснив следующие аспекты классов типов:
- Внешние, не реализованные внутри рассматриваемого класса
- Классы, которые мы не контролируем
- Возможность иметь несколько реализаций
- Закономерность
Отличным примером для демонстрации вышеупомянутых аспектов и различий между подходом класса типа и обычным подходом наследования являются java интерфейсы Comparable
и Comparator
.
Внешняя и внутренняя реализация
Для использования подхода наследования класс, экземпляры которого мы хотим сравнить, должен расширить Comparable
и добавить реализацию метода compareTo
. В противоположном случае, если мы используем подход класса типа, мы реализуем экземпляр Comparator
.
Классы, которые мы не контролируем
Невозможно использовать подход наследования в случаях, когда у нас нет контроля над классом, который мы хотим сравнить. Например, класс взят из внешней зависимости.
Это можно обойти, например, определив класс-обертку, что иногда может быть неудобно. Подход с использованием класса типа не имеет такого ограничения. Мы можем свободно реализовать экземпляр Comparator
для любого класса, который попадется нам под руку.
Одиночная и множественная реализация
Использование Comparable
ограничивает нас единственной реализацией Comparable. Это можно обойти, например, используя подклассы и определяя различные реализации для каждого подкласса. В зависимости от используемого языка мы можем определить одну или несколько реализаций класса типов. Хотя scala позволяет использовать несколько реализаций классов типов, ожидается, что они не будут определены на одном уровне разрешения. Подробнее об этом можно прочитать здесь. Haskell обычно не позволяет использовать несколько экземпляров классов типов. Хотя есть способы обойти это ограничение.
Законность
Каждый класс типов имеет набор законов, которым должен следовать каждый экземпляр. В случае класса типов Comparator
мы имеем, например, следующее:
if x.equals(y) then x.compareTo(y) == 0
if x.compareTo(y) == 0 then y.compareTo(x) == 0
if x.compareTo(y) < 0 and y.compareTo(z) < 0
then x.comapreTo(z) < 0
В целом это все, что можно сказать о классах типов. Хотя это всего лишь пример, существует множество классов типов, большинство из которых взято из теории категорий. Но вам определенно не нужно углубляться в эту тему, чтобы использовать их. В scala классы типов реализуются с помощью трейтов, которые довольно близки к интерфейсам java.
Когда речь заходит о реализации классов типов в scala, больше всего людей смущает не сама концепция классов типов, а магия имплицитов scala за кулисами. Магия необходима, потому что, в отличие от haskell, который определяет классы типов как первоклассную структуру.
В scala они не являются частью ядра языка, вместо этого они реализуются с помощью других возможностей языка, таких как неявные параметры и неявные преобразования. Неявные параметры иногда могут сбивать с толку, особенно новичков, но не позволяйте себе отчаиваться.
В результате понятие сравнения экземпляра определенного класса становится внешним. Определения методов, использующие подход наследования, выглядят следующим образом:
// java
public <A extends Comparable<A>> ResultType methodWhichNeedsToCompareInstances(A a)
// scala
def methodWhichNeedsToCompareInstances[A <: Comparable[A]](a: A): ResultType
При использовании подхода класса типа они выглядят следующим образом:
// java
public <A> ResultType methodWhichNeedsToCompareInstances(A a)(Comparator<A> comparator)
// scala
def methodWhichNeedsToCompareInstances[A](a: A)
(implicit comparator: Comparator[A]): ResultType
Если мне удалось заинтересовать вас, вы можете взглянуть на одну из следующих библиотек, таких как cats, scalaz для scala и vavr для java, которые содержат определения классов типов и их реализации для распространенных типов.