В информатике, класс типа представляет собой тип системы конструкции, которая поддерживает специальный полиморфизм. Это достигается путем добавления ограничений на переменные типа в параметрически полиморфных типах. Такое ограничение, как правило, включает в себя класс типа T
и переменный типа a
, а также средства, которые a
могут быть инстанцированным только к типу, члены которой поддерживают перегруженные операции, связанные с T
.
Классы типов были впервые реализованы на языке программирования Haskell после того, как они были впервые предложены Филипом Уодлером и Стивеном Блоттом как расширение «eqtypes» в стандартном машинном обучении, и первоначально были задуманы как способ принципиальной реализации перегруженных арифметических операторов и операторов равенства. В отличие от «eqtypes» Standard ML, перегрузка оператора равенства посредством использования классов типов в Haskell не требует обширной модификации внешнего интерфейса компилятора или базовой системы типов.
С момента их создания было обнаружено множество других приложений классов типов.
Классы типов определяются путем указания набора имен функций или констант вместе с их соответствующими типами, которые должны существовать для каждого типа, принадлежащего этому классу. В Haskell типы можно параметризовать; класс типа, Eq
предназначенный для содержания типов, допускающих равенство, будет объявлен следующим образом:
class Eq a where (==):: a -gt; a -gt; Bool (/=):: a -gt; a -gt; Bool
где a
- один экземпляр класса типа Eq
и a
определяет сигнатуры функций для 2 функций (функции равенства и неравенства), каждая из которых принимает 2 аргумента типа a
и возвращает логическое значение.
Тип переменной a
имеет вид (также известный как в последнем GHC выпуска), а это означает, что вид является Type
Eq
Eq:: Type -gt; Constraint
Объявление может быть прочитано как указание на то, что «тип a
принадлежит классу типов, Eq
если есть названные функции (==)
и (/=)
соответствующие типы, определенные в нем». Затем программист может определить функцию elem
(которая определяет, находится ли элемент в списке) следующим образом:
elem:: Eq a =gt; a -gt; [a] -gt; Bool elem y [] = False elem y (x:xs) = (x == y) || elem y xs
Функция elem
имеет тип a -gt; [a] -gt; Bool
с контекстом Eq a
, который ограничивает типы, которые a
могут варьироваться, до тех, a
которые принадлежат к Eq
классу типов. ( Примечание: Haskell =gt;
можно назвать «ограничением класса».)
Программист может сделать любой тип t
членом данного класса типа C
, используя объявление экземпляра, которое определяет реализации всех C
методов для конкретного типа t
. Например, если программист определяет новый тип данных t
, он может затем сделать этот новый тип экземпляром Eq
, предоставив функцию равенства значений типа t
любым способом, который они сочтут нужным. Как только они это сделают, они могут использовать эту функцию elem
для [t]
списков элементов типа t
.
Обратите внимание, что классы типов отличаются от классов объектно-ориентированных языков программирования. В частности, Eq
это не тип: не существует такой вещи, как значение типа Eq
.
Классы типов тесно связаны с параметрическим полиморфизмом. Например, обратите внимание, что elem
указанный выше тип был бы параметрически полиморфным типом, a -gt; [a] -gt; Bool
если бы не ограничение класса типа " Eq a =gt;
".
Класс типа не обязательно должен принимать переменную типа, Type
но может принимать переменную любого типа. Эти классы типов с более высокими типами иногда называют классами конструкторов (упомянутые конструкторы являются конструкторами типов, такими как Maybe
, а не конструкторами данных, такими как Just
). Примером может служить Monad
класс:
class Monad m where return:: a -gt; m a (gt;gt;=):: m a -gt; (a -gt; m b) -gt; m b
Тот факт, что m применяется к переменной типа, указывает, что она имеет вид Type -gt; Type
, то есть принимает тип и возвращает тип, вид Monad
такой:
Monad:: (Type -gt; Type) -gt; Constraint
Классы типов допускают несколько параметров типа, поэтому классы типов можно рассматривать как отношения между типами. Например, в стандартной библиотеке GHC класс IArray
выражает общий интерфейс неизменяемого массива. В этом классе ограничение класса типа IArray a e
означает, что a
это тип массива, содержащий элементы типа e
. (Это ограничение полиморфизма используется, например, для реализации неупакованных типов массивов.)
Как и мультиметоды, классы типов с несколькими параметрами поддерживают вызов различных реализаций метода в зависимости от типов нескольких аргументов и действительно возвращаемых типов. Классы с несколькими параметрами не требуют поиска метода для вызова при каждом вызове во время выполнения; скорее вызываемый метод сначала компилируется и сохраняется в словаре экземпляра класса типа, как и в случае с классами типов с одним параметром.
Код Haskell, использующий классы типов с несколькими параметрами, непереносим, поскольку эта функция не является частью стандарта Haskell 98. Популярные реализации Haskell, GHC и Hugs, поддерживают классы типов с несколькими параметрами.
В Haskell классы типов были усовершенствованы, чтобы позволить программисту объявлять функциональные зависимости между параметрами типа - концепция, вдохновленная теорией реляционных баз данных. То есть программист может утверждать, что данное присвоение некоторого подмножества параметров типа однозначно определяет остальные параметры типа. Например, общая монада, m
которая несет параметр состояния типа, s
удовлетворяет ограничению класса типа Monad.State s m
. В этом ограничении есть функциональная зависимость m -gt; s
. Это означает, что для данной монады m
типа class Monad.State
тип состояния, доступный из, m
определяется однозначно. Это помогает компилятору в выводе типов, а также помогает программисту в программировании, ориентированном на типы.
Саймон Пейтон-Джонс возражает против введения функциональных зависимостей в Haskell по причине сложности.
Классы типов и неявные параметры очень похожи по своей природе, хотя и не совсем одинаковы. Полиморфная функция с ограничением класса типа, например:
sum:: Num a =gt; [a] -gt; a
может интуитивно восприниматься как функция, которая неявно принимает экземпляр Num
:
sum_:: Num_ a -gt; [a] -gt; a
Экземпляр Num_ a
- это, по сути, запись, содержащая определение экземпляра Num a
. (Фактически, именно так классы типов реализуются под капотом компилятора Glasgow Haskell.)
Однако есть важное отличие: неявные параметры более гибкие - вы можете передавать разные экземпляры Num Int
. Напротив, классы типов применяют так называемое свойство согласованности, которое требует, чтобы для любого данного типа был только один уникальный выбор экземпляра. Свойство coherence делает классы типов в некоторой степени антимодульными, поэтому не рекомендуется использовать бесхозные экземпляры (экземпляры, которые определены в модуле, который не содержит ни класс, ни интересующий тип). С другой стороны, согласованность добавляет языку дополнительный уровень безопасности, предоставляя программисту гарантию того, что две непересекающиеся части одного и того же кода будут совместно использовать один и тот же экземпляр.
Например, упорядоченный набор (типа Set a
) требует полного упорядочения элементов (типа a
) для функционирования. Об этом может свидетельствовать ограничение Ord a
, которое определяет оператор сравнения для элементов. Однако существует множество способов навести тотальный порядок. Поскольку алгоритмы набора обычно нетерпимы к изменениям в порядке после создания набора, передача несовместимого экземпляра Ord a
функции, которые работают с набором, может привести к неверным результатам (или сбоям). Таким образом, обеспечение согласованности Ord a
в этом конкретном сценарии имеет решающее значение.
Экземпляры (или «словари») в классах типов Scala - это просто обычные значения в языке, а не совершенно отдельный вид сущности. Хотя эти экземпляры по умолчанию предоставляются путем нахождения подходящих экземпляров в области видимости для использования в качестве неявных фактических параметров для явно объявленных неявных формальных параметров, тот факт, что они являются обычными значениями, означает, что они могут быть предоставлены явно для устранения неоднозначности. В результате классы типов Scala не удовлетворяют свойству согласованности и фактически являются синтаксическим сахаром для неявных параметров.
Это пример из документации Cats:
// A type class to provide textual representation trait Show[A] { def show(f: A): String } // A polymorphic function that works only when there is an implicit // instance of Show[A] available def log[A](a: A)(implicit s: Show[A]) = println(s.show(a)) // An instance for String implicit val stringShow = new Show[String] { def show(s: String) = s } // The parameter stringShow was inserted by the compiler. scalagt; log("a string") a string
Coq (начиная с версии 8.2) также поддерживает классы типов путем вывода соответствующих экземпляров. Последние версии Agda 2 также предоставляют аналогичную функцию, называемую «аргументами экземпляра».
В стандартном ML механизм «типов равенства» примерно соответствует встроенному классу типов Haskell Eq
, но все операторы равенства выводятся компилятором автоматически. Контроль программиста над процессом ограничивается указанием того, какие компоненты типа в структуре являются типами равенства, а какие переменные типа в диапазоне полиморфных типов по сравнению с типами равенства.
Модули и функторы SML и OCaml могут играть роль, аналогичную роли классов типов Haskell, главное отличие заключается в роли вывода типов, что делает классы типов подходящими для специального полиморфизма. Объектно-ориентированное подмножество OCaml - это еще один подход, который в некоторой степени сопоставим с подходом классов типов.
Аналогичным понятием для перегруженных данных (реализованным в GHC ) является понятие семейства типов.
В C ++, в частности, C ++ 20 поддерживает классы типов, использующие концепции (C ++). В качестве иллюстрации вышеупомянутый пример Haskell класса типов Eq будет записан как
template lt;typename Tgt; concept Equal = requires (T a, T b) { { a == b } -gt; std::convertible_tolt;boolgt;; { a != b } -gt; std::convertible_tolt;boolgt;; };
В Clean классы типов похожи на Haskell, но имеют немного другой синтаксис.
Rust поддерживает трейты, которые представляют собой ограниченную форму классов типов с согласованностью.
У Mercury есть классы типов, хотя они не совсем такие, как в Haskell.
В Scala классы типов - это идиома программирования, которая может быть реализована с помощью существующих языковых функций, таких как неявные параметры, а не отдельной языковой функции как таковой. Благодаря тому, как они реализованы в Scala, можно явно указать, какой экземпляр класса типа использовать для типа в конкретном месте кода в случае двусмысленности. Однако это не обязательно является преимуществом, поскольку экземпляры классов неоднозначного типа могут быть подвержены ошибкам.
Помощник доказательства Coq также поддерживает классы типов в последних версиях. В отличие от обычных языков программирования, в Coq любые законы класса типов (например, законы монад), которые изложены в определении класса типа, должны быть математически подтверждены для каждого экземпляра класса типа перед их использованием.
Monad
пример типа класса)