В информатике класс типов — это конструкция системы типов , поддерживающая специальный полиморфизм . Это достигается за счет добавления ограничений на переменные типа в параметрически полиморфных типах. Такое ограничение обычно включает класс типа T
и переменную типа a
и означает, что его a
экземпляр может быть создан только для типа, члены которого поддерживают перегруженные операции, связанные с T
.
Классы типов были впервые реализованы на языке программирования Haskell после того, как они были впервые предложены Филипом Уодлером и Стивеном Блоттом в качестве расширения «eqtypes» в Standard ML , [1] [2] и первоначально были задуманы как способ реализации перегруженной арифметики и равенства. операторы в принципиальном порядке. [3] [2] В отличие от «eqtypes» Standard ML, перегрузка оператора равенства посредством использования классов типов в Haskell не требует обширной модификации интерфейса компилятора или базовой системы типов. [4]
Классы типов определяются путем указания набора имен функций или констант вместе с соответствующими типами, которые должны существовать для каждого типа, принадлежащего классу. В Haskell типы можно параметризовать; класс типов Eq
, предназначенный для содержания типов, допускающих равенство, будет объявлен следующим образом:
class Eq a где ( == ) :: a -> a -> Bool ( /= ) :: a -> a -> Bool
где a
— один экземпляр класса типов Eq
и a
определяет сигнатуры функций для двух функций (функций равенства и неравенства), каждая из которых принимает по два аргумента типа a
и возвращает логическое значение.
Переменная типа a
имеет вид ( также известный как в последней версии GHC ), [5] что означает, что типType
Eq
Eq :: Тип -> Ограничение
Объявление можно прочитать как заявление о том, что «тип a
принадлежит классу типов , Eq
если на нем определены функции с именами (==)
и (/=)
соответствующих типов». Затем программист может определить функцию elem
(которая определяет, находится ли элемент в списке) следующим образом:
elem :: Eq a => a -> [ a ] -> Bool elem y [] = False elem y ( x : xs ) = ( x == y ) || элемент y хз
Функция elem
имеет тип a -> [a] -> Bool
с контекстом Eq a
, который ограничивает типы, которые a
могут варьироваться до тех a
, которые принадлежат Eq
классу типов. ( Примечание : Haskell =>
можно назвать «ограничением класса».)
Программист может сделать любой тип t
членом данного класса типов, C
используя объявление экземпляра , определяющее реализации всех C
методов для конкретного типа t
. Например, если программист определяет новый тип данных t
, он может затем сделать этот новый тип экземпляром Eq
, предоставив функцию равенства значений типа t
любым удобным для него способом. Сделав это, они могут использовать функцию elem
on [t]
, то есть списки элементов типа t
.
Обратите внимание, что классы типов отличаются от классов объектно-ориентированных языков программирования. В частности, Eq
это не тип: не существует такого понятия, как значение типа Eq
.
Классы типов тесно связаны с параметрическим полиморфизмом. Например, обратите внимание, что тип, elem
указанный выше, был бы параметрически полиморфным типом, a -> [a] -> Bool
если бы не ограничение класса типа " Eq a =>
".
Класс типа не обязательно должен принимать переменную типа определенного типа Type
, но может принимать переменную любого типа. Эти классы типов с более высокими видами иногда называются классами-конструкторами (конструкторы, о которых идет речь, являются конструкторами типов, такими как Maybe
, а не конструкторами данных, такими как Just
). Примером является Monad
класс:
класс Monad m , где return :: a -> m a ( >>= ) :: m a -> ( a -> m b ) -> m b
Тот факт, что m применяется к переменной типа, указывает на то, что она имеет вид Type -> Type
, т. е. она принимает тип и возвращает тип, вид Monad
таким образом:
Монада :: ( Тип -> Тип ) -> Ограничение
Классы типов допускают несколько параметров типа, поэтому классы типов можно рассматривать как отношения к типам. [6] Например, в стандартной библиотеке GHC класс IArray
выражает общий интерфейс неизменяемого массива. В этом классе ограничение класса типа IArray a e
означает, что a
это тип массива, содержащий элементы типа e
. (Это ограничение на полиморфизм используется, например, для реализации неупакованных типов массивов.)
Как и мультиметоды , классы типов с несколькими параметрами поддерживают вызов различных реализаций метода в зависимости от типов нескольких аргументов и даже типов возвращаемых значений. Классы типов с несколькими параметрами не требуют поиска метода для каждого вызова во время выполнения; [7] : минута 25:12, скорее, вызываемый метод сначала компилируется и сохраняется в словаре экземпляра класса типа, как и в случае с классами типов с одним параметром.
Код Haskell, использующий классы типов с несколькими параметрами, не является переносимым, поскольку эта функция не является частью стандарта Haskell 98. Популярные реализации Haskell, GHC и Hugs , поддерживают классы типов с несколькими параметрами.
В Haskell классы типов были усовершенствованы, чтобы позволить программисту объявлять функциональные зависимости между параметрами типа — концепция, вдохновленная теорией реляционных баз данных . [8] [9] То есть программист может утверждать, что данное присвоение некоторого подмножества параметров типа однозначно определяет остальные параметры типа. Например, общая монада m
, которая несет параметр состояния типа, s
удовлетворяет ограничению класса типа Monad.State s m
. В этом ограничении существует функциональная зависимость m -> s
. Это означает, что для данной монады m
типа class Monad.State
тип состояния, доступный из m
, определен однозначно. Это помогает компилятору в выводе типа , а также помогает программисту в типизированном программировании.
Саймон Пейтон-Джонс возражал против введения функциональных зависимостей в Haskell по причине сложности. [10]
Классы типов и неявные параметры очень похожи по своей природе, хотя и не совсем одинаковы. Полиморфная функция с ограничением класса типа, например:
сумма :: Num a => [ a ] -> a
можно интуитивно рассматривать как функцию, которая неявно принимает экземпляр Num
:
sum_ :: Num_ a -> [ a ] -> a
По сути , экземпляр Num_ a
представляет собой запись, содержащую определение экземпляра Num a
. (Фактически именно так классы типов реализуются под капотом компилятора Glasgow Haskell.)
Однако есть важное отличие: неявные параметры более гибкие — вы можете передавать разные экземпляры Num Int
. Напротив, классы типов реализуют так называемое свойство согласованности , которое требует, чтобы для любого данного типа был только один уникальный выбор экземпляра. Свойство когерентности делает классы типов несколько антимодульными, поэтому настоятельно не рекомендуется использовать потерянные экземпляры (экземпляры, определенные в модуле, который не содержит ни класс, ни интересующий тип). С другой стороны, согласованность добавляет языку дополнительный уровень безопасности, давая программисту гарантию того, что две непересекающиеся части одного и того же кода будут использовать один и тот же экземпляр. [11]
Например, для функционирования упорядоченного набора (типа Set a
) требуется полный порядок элементов (типа ). a
Об этом может свидетельствовать ограничение Ord a
, которое определяет оператор сравнения элементов. Однако существует множество способов навести полный порядок. Поскольку алгоритмы множеств обычно нетерпимы к изменениям порядка после создания набора, передача несовместимого экземпляра функциям, Ord a
которые работают с набором, может привести к неверным результатам (или сбоям). Таким образом, обеспечение согласованности Ord a
в этом конкретном сценарии имеет решающее значение.
Экземпляры (или «словари») в классах типов Scala — это просто обычные значения языка, а не совершенно отдельный вид сущностей. [12] [13] Хотя эти экземпляры по умолчанию предоставляются путем поиска соответствующих экземпляров в области видимости, которые будут использоваться в качестве неявных фактических параметров для явно объявленных неявных формальных параметров, тот факт, что они являются обычными значениями, означает, что они могут быть предоставлены явно, разрешить двусмысленность. В результате классы типов Scala не удовлетворяют свойству связности и фактически являются синтаксическим сахаром для неявных параметров.
Это пример из документации Cats [14] :
// Класс типа для текстового представления типажа Show [ A ] { def show ( f : A ): String } // Полиморфная функция, которая работает только при наличии // неявного экземпляра Show[A] def log [ A ]( a : A )( неявное s : Show [ A ]) = println ( s . show ( a ) ) // Экземпляр для String неявное val stringShow = new Show [ String ] { def show ( s : String ) = s } // Параметр stringShow был вставлен компилятором. scala > log ( «строка » ) строка
Coq (версия 8.2 и более поздние) также поддерживает классы типов, выводя соответствующие экземпляры. [15] Последние версии Agda 2 также предоставляют аналогичную функцию, называемую «аргументами экземпляра». [16]
В Standard ML механизм «типов равенства» примерно соответствует встроенному классу типов Haskell Eq
, но все операторы равенства выводятся компилятором автоматически. Программистский контроль над процессом ограничивается указанием того, какие компоненты типов в структуре являются типами равенства, а переменные каких типов в диапазоне полиморфных типов превышают типы равенства.
Модули и функторы SML и OCaml могут играть роль, аналогичную роли классов типов Haskell, причем принципиальным отличием является роль вывода типа, что делает классы типов пригодными для специального полиморфизма. [17] Объектно-ориентированное подмножество OCaml — это еще один подход, который в некоторой степени сравним с подходом к классам типов.
Аналогичным понятием для перегруженных данных (реализованным в GHC ) является семейство типов . [18]
В C++, в частности, в C++20 имеется поддержка классов типов с использованием Concepts (C++) . В качестве иллюстрации приведенный выше пример класса типов Eq на Haskell можно записать как
шаблон < имя типа T > концепция Equal = требует ( T a , T b ) { { a == b } -> std :: Convertible_to < bool > ; { a != b } -> std :: Convertible_to < bool > ; };
Классы типов в Clean похожи на Haskell, но имеют немного другой синтаксис.
Rust поддерживает типажи , которые представляют собой ограниченную форму согласованных классов типов. [19]
В Mercury есть классы типов, хотя они не совсем такие, как в Haskell. [ нужны дальнейшие объяснения ]
В Scala классы типов — это идиома программирования , которая может быть реализована с использованием существующих функций языка, таких как неявные параметры, а не отдельной функции языка как таковой. Благодаря тому, как они реализованы в Scala, в случае неоднозначности можно явно указать, какой экземпляр класса типа использовать для типа в определенном месте кода. Однако это не обязательно является преимуществом, поскольку экземпляры классов неоднозначных типов могут быть подвержены ошибкам.
Помощник по доказательству Coq также поддерживает классы типов в последних версиях. В отличие от обычных языков программирования, в Coq любые законы класса типов (например, законы монады), изложенные в определении класса типов, должны быть математически доказаны для каждого экземпляра класса типов перед их использованием.
Monad
пример класса типа)Data.Kind
появился в версии 8 компилятора Glasgow Haskell.