В теории языков программирования и теории типов полиморфизм — это использование одного символа для представления нескольких различных типов. [1]
В объектно-ориентированном программировании полиморфизм — это предоставление одного интерфейса для сущностей разных типов данных . [2] Концепция заимствована из принципа в биологии, согласно которому организм или вид может иметь много разных форм или стадий. [3]
Наиболее общепризнанными основными формами полиморфизма являются:
Интерес к полиморфным системам типов значительно возрос в 1990-х годах, и практические реализации начали появляться к концу десятилетия. Полиморфизм ad hoc и параметрический полиморфизм были первоначально описаны в работе Кристофера Стрейчи « Основные концепции языков программирования » [5] , где они перечислены как «два основных класса» полиморфизма. Полиморфизм ad hoc был особенностью ALGOL 68 , в то время как параметрический полиморфизм был основной особенностью системы типов ML .
В статье 1985 года Питер Вегнер и Лука Карделли ввели термин «полиморфизм включения» для моделирования подтипов и наследования , [1] ссылаясь на Simula как на первый язык программирования, реализовавший его.
Кристофер Стрейчи выбрал термин ad hoc polymorphism для обозначения полиморфных функций, которые могут применяться к аргументам разных типов, но которые ведут себя по-разному в зависимости от типа аргумента, к которому они применяются (также известно как перегрузка функций или перегрузка операторов ). [5] Термин « ad hoc » в этом контексте не является уничижительным: вместо этого он означает, что эта форма полиморфизма не является фундаментальной особенностью системы типов. В примере Java ниже Add
функции, похоже, работают в общем случае над двумя типами ( целое число и строка ) при рассмотрении вызовов, но компилятор считает их двумя совершенно разными функциями для всех намерений и целей:
class AdHocPolymorphic { public String add ( int x , int y ) { return "Сумма: " + ( x + y ); } public String add ( String name ) { return "Добавлено " + name ; } } public class adhoc { public static void main ( String [] args ) { AdHocPolymorphic poly = new AdHocPolymorphic (); System.out.println ( poly.add ( 1,2 ) ); // выводит " Сумма : 3 " System.out.println ( poly.add ( " Джей " ) ) ; // выводит " Добавлен Джей " } }
В динамически типизированных языках ситуация может быть более сложной, поскольку правильная функция, которую необходимо вызвать, может быть определена только во время выполнения.
Неявное преобразование типов также определяется как форма полиморфизма, называемая «полиморфизмом принуждения». [1] [6]
Параметрический полиморфизм позволяет записывать функцию или тип данных обобщенно, так что они могут обрабатывать значения единообразно , независимо от их типа. [7] Параметрический полиморфизм — это способ сделать язык более выразительным, сохраняя при этом полную статическую безопасность типов .
Концепция параметрического полиморфизма применима как к типам данных , так и к функциям . Функция, которая может вычисляться или применяться к значениям разных типов, называется полиморфной функцией. Тип данных, который может казаться обобщенным типом (например, список с элементами произвольного типа), обозначается как полиморфный тип данных, как и обобщенный тип, из которого сделаны такие специализации.
Параметрический полиморфизм повсеместно распространен в функциональном программировании, где его часто называют просто «полиморфизмом». Следующий пример на Haskell показывает параметризованный тип данных списка и две параметрически полиморфные функции на них:
Список данных a = Ноль | Минусы a ( Список a ) длина :: Список a -> Целое число длина Ноль = 0 длина ( Cons x xs ) = 1 + длина xs map :: ( a -> b ) -> Список a -> Список b map f Nil = Nil map f ( Cons x xs ) = Cons ( f x ) ( map f xs )
Параметрический полиморфизм также доступен в нескольких объектно-ориентированных языках. Например, шаблоны в C++ и D или под названием generics в C# , Delphi , Java и Go :
класс List < T > { класс Node < T > { T elem ; Node < T > next ; } Node < T > head ; int length () { ... } } Список <B> map ( Func <A , B > f , Список <A > xs ) { ... }
Джон К. Рейнольдс (а позже Жан-Ив Жирар ) формально разработал это понятие полиморфизма как расширение лямбда-исчисления (называемое полиморфным лямбда-исчислением или Системой F ). Любая параметрически полиморфная функция обязательно ограничена в том, что она может делать, работая над формой данных вместо их значения, что приводит к концепции параметричности .
Некоторые языки используют идею подтипирования (также называемую полиморфизмом подтипов или полиморфизмом включения ) для ограничения диапазона типов, которые могут использоваться в конкретном случае полиморфизма. В этих языках подтипирование позволяет записать функцию, которая принимает объект определенного типа T , но также работает правильно, если передан объект, принадлежащий типу S , который является подтипом T (согласно принципу подстановки Лисков ). Это отношение типов иногда записывается как S <: T . И наоборот, T называется супертипом S , пишется как T : > S . Полиморфизм подтипов обычно разрешается динамически (см. ниже).
В следующем примере Java кошки и собаки сделаны подтипами домашних животных. Процедура letsHear()
принимает домашнее животное, но также будет работать правильно, если ей передан подтип:
абстрактный класс Pet { абстрактный String speak (); } класс Cat расширяет Pet { String speak () { return "Мяу!" ; } } класс Собака расширяет Домашнее животное { String speak () { return "Гав!" ; } } static void letsHear ( final Pet pet ) { println ( pet . speak ()); } static void main ( String [] args ) { letsHear ( new Cat ()); letsHear ( new Dog ()); }
В другом примере, если Number , Rational и Integer являются типами, такими что Number :> Rational и Number :> Integer ( Rational и Integer как подтипы типа Number , который является их супертипом), функция, написанная для приема Number, будет работать одинаково хорошо как при передаче Integer или Rational , так и при передаче Number . Фактический тип объекта может быть скрыт от клиентов в черном ящике и доступен через идентификатор объекта . Если тип Number является абстрактным , может даже не быть возможности получить в руки объект, наиболее производным типом которого является Number (см. абстрактный тип данных , абстрактный класс ). Этот конкретный вид иерархии типов известен, особенно в контексте языка Scheme , как числовая башня , и обычно содержит гораздо больше типов.
Объектно-ориентированные языки программирования предлагают полиморфизм подтипов с использованием подклассов (также известных как наследование ). В типичных реализациях каждый класс содержит то, что называется виртуальной таблицей (кратко называемой vtable ) — таблицей функций, которые реализуют полиморфную часть интерфейса класса, — и каждый объект содержит указатель на vtable своего класса, к которому затем обращаются всякий раз, когда вызывается полиморфный метод. Этот механизм является примером:
this
), поэтому типы времени выполнения других аргументов совершенно не имеют значения.То же самое касается и большинства других популярных объектных систем. Однако некоторые, например Common Lisp Object System , предоставляют множественную диспетчеризацию , при которой вызовы методов полиморфны во всех аргументах.
Взаимодействие параметрического полиморфизма и подтипирования приводит к концепциям дисперсии и ограниченной квантификации .
Полиморфизм строк [8] — это похожая, но отличная от подтипирования концепция. Она имеет дело со структурными типами . Она позволяет использовать все значения, типы которых обладают определенными свойствами, не теряя оставшуюся информацию о типе.
Связанное понятие — политипизм (или универсальность типа данных ). Политипическая функция более общая, чем полиморфная, и в такой функции, «хотя можно предоставить фиксированные специальные случаи для определенных типов данных, отсутствует специальный комбинатор». [9]
Полиморфизм рангов является одной из определяющих особенностей языков программирования массивов , таких как APL . Суть модели полиморфного программирования рангов заключается в неявной обработке всех операций как агрегатных операций, применимых к массивам с произвольным количеством измерений [10] , что означает, что полиморфизм рангов позволяет определять функции для работы с массивами любой формы и размера.
Полиморфизм можно различить по тому, когда выбирается реализация: статически (во время компиляции) или динамически (во время выполнения, обычно через виртуальную функцию ). Это известно соответственно как статическая диспетчеризация и динамическая диспетчеризация , а соответствующие формы полиморфизма соответственно называются статическим полиморфизмом и динамическим полиморфизмом .
Статический полиморфизм выполняется быстрее, поскольку нет динамических накладных расходов на диспетчеризацию, но требует дополнительной поддержки компилятора. Кроме того, статический полиморфизм позволяет компиляторам проводить более глубокий статический анализ (особенно для оптимизации), инструментам анализа исходного кода и людям-читателям (программистам). Динамический полиморфизм более гибок, но медленнее — например, динамический полиморфизм допускает утиную типизацию, а динамически связанная библиотека может работать с объектами, не зная их полного типа.
Статический полиморфизм обычно встречается в полиморфизме ad hoc и параметрическом полиморфизме, тогда как динамический полиморфизм обычен для полиморфизма подтипов. Однако можно достичь статического полиморфизма с подтипированием посредством более сложного использования шаблонного метапрограммирования , а именно любопытно повторяющегося шаблонного шаблона .
Когда полиморфизм раскрывается через библиотеку , статический полиморфизм становится невозможным для динамических библиотек , поскольку нет способа узнать, какие типы параметров используются при построении общего объекта . В то время как такие языки, как C++ и Rust, используют мономорфизированные шаблоны, язык программирования Swift широко использует динамическую диспетчеризацию для построения двоичного интерфейса приложения для этих библиотек по умолчанию. В результате больше кода может быть совместно использовано для уменьшения размера системы за счет накладных расходов времени выполнения. [11]
полиморфизм — предоставление единого интерфейса для сущностей разных типов.