Класс типов (type class) — это абстрактный параметризованный тип,
который позволяет добавлять новое поведение к любому закрытому типу данных без использования подтипов.
Если вы пришли с Java, то можно думать о классах типов как о чем-то вроде java.util.Comparator[T]
.
В статье “Type Classes as Objects and Implicits” (2010 г.) обсуждаются основные идеи, лежащие в основе классов типов в Scala. Несмотря на то, что в статье используется более старая версия Scala, идеи актуальны и по сей день.
Этот стиль программирования полезен во многих случаях, например:
- выражение того, как тип, которым вы не владеете, например, из стандартной или сторонней библиотеки, соответствует такому поведению
- добавление поведения к нескольким типам без введения отношений подтипов между этими типами (например, когда один расширяет другой)
Классы типов — это трейты с одним или несколькими параметрами,
реализации которых предоставляются в виде экземпляров given
в Scala 3 или implicit
значений в Scala 2.
Пример
Например, Show
- хорошо известный класс типов в Haskell, и в следующем коде показан один из способов его реализации в Scala.
Если предположить, что классы Scala не содержат метода toString
, то можно определить класс типов Show
,
чтобы добавить это поведение к любому типу, который вы хотите преобразовать в пользовательскую строку.
Класс типов
Первым шагом в создании класса типов является объявление параметризованного trait, содержащего один или несколько абстрактных методов.
Поскольку Showable
содержит только один метод с именем show
, он записывается так:
// класс типов
trait Showable[A] {
def show(a: A): String
}
// класс типов
trait Showable[A]:
extension (a: A) def show: String
Обратите внимание, что этот подход близок к обычному объектно-ориентированному подходу,
когда обычно trait Show
определяется следующим образом:
// a trait
trait Show {
def show: String
}
// a trait
trait Show:
def show: String
Следует отметить несколько важных моментов:
- Классы типов, например,
Showable
принимают параметр типаA
, чтобы указать, для какого типа мы предоставляем реализациюshow
; в отличие от классических трейтов, наподобиеShow
. - Чтобы добавить функциональность
show
к определенному типуA
, классический трейт требует наследованияA extends Show
, в то время как для классов типов нам требуется реализацияShowable[A]
. - В Scala 3, чтобы разрешить один и тот же синтаксис вызова метода в обоих случаях
Showable
, который имитирует синтаксисShow
, мы определяемShowable.show
как метод расширения.
Реализация конкретных экземпляров
Следующий шаг — определить, какие классы Showable
должны работать в вашем приложении, а затем реализовать для них это поведение.
Например, для реализации Showable
следующего класса Person
:
case class Person(firstName: String, lastName: String)
необходимо определить одно каноническое значение типа Showable[Person]
, т.е. экземпляр Showable
для типа Person
,
как показано в следующем примере кода:
implicit val showablePerson: Showable[Person] = new Showable[Person] {
def show(p: Person): String =
s"${p.firstName} ${p.lastName}"
}
given Showable[Person] with
extension (p: Person) def show: String =
s"${p.firstName} ${p.lastName}"
Использование класса типов
Теперь вы можете использовать этот класс типов следующим образом:
val person = Person("John", "Doe")
println(showablePerson.show(person))
Обратите внимание, что на практике классы типов обычно используются со значениями, тип которых неизвестен,
в отличие от type Person
, как показано в следующем разделе.
val person = Person("John", "Doe")
println(person.show)
Опять же, если бы в Scala не было метода toString
, доступного для каждого класса, вы могли бы использовать эту технику,
чтобы добавить поведение Showable
к любому классу, который вы хотите преобразовать в String
.
Написание методов, использующих класс типов
Как и в случае с наследованием, вы можете определить методы, которые используют Showable
в качестве параметра типа:
def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit =
as.foreach(a => println(showable.show(a)))
showAll(List(Person("Jane"), Person("Mary")))
def showAll[A: Showable](as: List[A]): Unit =
as.foreach(a => println(a.show))
showAll(List(Person("Jane"), Person("Mary")))
Класс типов с несколькими методами
Обратите внимание: если вы хотите создать класс типов с несколькими методами, исходный синтаксис выглядит следующим образом:
trait HasLegs[A] {
def walk(a: A): Unit
def run(a: A): Unit
}
trait HasLegs[A]:
extension (a: A)
def walk(): Unit
def run(): Unit
Пример из реального мира
В качестве примера из реального мира, как классы типов используются в Scala 3,
см. обсуждение CanEqual
в разделе Multiversal Equality.
Contributors to this page:
Contents
- Введение
- Возможности Scala
- Почему Scala 3?
- Почувствуй Scala
- Пример 'Hello, World!'
- REPL
- Переменные и типы данных
- Структуры управления
- Моделирование данных
- Методы
- Функции первого класса
- Одноэлементные объекты
- Коллекции
- Контекстные абстракции
- Верхнеуровневые определения
- Обзор
- Первый взгляд на типы
- Интерполяция строк
- Структуры управления
- Моделирование предметной области
- Инструменты
- Моделирование ООП
- Моделирование ФП
- Методы
- Особенности методов
- Main методы в Scala 3
- Обзор
- Функции
- Анонимные функции
- Параметры функции
- Eta расширение
- Функции высшего порядка
- Собственный map
- Создание метода, возвращающего функцию
- Обзор
- Пакеты и импорт
- Коллекции в Scala
- Типы коллекций
- Методы в коллекциях
- Обзор
- Функциональное программирование
- Что такое функциональное программирование?
- Неизменяемые значения
- Чистые функции
- Функции — это значения
- Функциональная обработка ошибок
- Обзор
- Типы и система типов
- Определение типов
- Параметризованные типы
- Пересечение типов
- Объединение типов
- Алгебраические типы данных
- Вариантность
- Непрозрачные типы
- Структурные типы
- Зависимые типы функций
- Другие типы
- Контекстные абстракции
- Методы расширения
- Параметры контекста
- Контекстные границы
- Given импорты
- Классы типов
- Многостороннее равенство
- Неявное преобразование типов
- Обзор
- Параллелизм
- Scala утилиты
- Сборка и тестирование проектов Scala с помощью Sbt
- Рабочие листы
- Взаимодействие с Java