Подтипирование

Из Википедии, бесплатной энциклопедии

В теории языков программирования подтипирование ( также называемое полиморфизмом подтипов или полиморфизмом включения ) является формой полиморфизма типов . Подтип тип — это данных , который связан с другим типом данных ( супертипом ) некоторым понятием заменяемости , что означает, что программные элементы (обычно подпрограммы или функции), написанные для работы с элементами супертипа, также могут работать с элементами подтипа.

Если S является подтипом T, отношение подтипа (записанное как S <: T , S T , [1] или S ≤: T ) означает, что любой термин типа S можно безопасно использовать в любом контексте , где ожидается термин типа T. Точная семантика подтипирования здесь решающим образом зависит от того, насколько «безопасно использовать» и «любой контекст» определяются данным формализмом типов или языком программирования . Система типов языка программирования по существу определяет свое собственное отношение подтипирования, которое вполне может быть тривиальным , если язык не поддерживает (или очень мало) механизмов преобразования.

Из-за отношения подтипирования термин может принадлежать более чем к одному типу. Таким образом, подтипирование является формой полиморфизма типов. В объектно-ориентированном программировании термин «полиморфизм» обычно используется для обозначения исключительно полиморфизма этого подтипа , в то время как методы параметрического полиморфизма будут считаться общим программированием .

Языки функционального программирования часто допускают подтипирование записей . Следовательно, просто типизированное лямбда-исчисление , дополненное типами записей, является, пожалуй, простейшей теоретической постановкой, в которой можно определить и изучить полезное понятие подтипирования. [2] Поскольку полученное в результате исчисление позволяет терминам иметь более одного типа, оно больше не является «простой» теорией типов . Поскольку языки функционального программирования по определению поддерживают литералы функций , которые также могут храниться в записях, типы записей с подтипами предоставляют некоторые возможности объектно-ориентированного программирования. Как правило, языки функционального программирования также предоставляют некоторую, обычно ограниченную, форму параметрического полиморфизма. В теоретическом плане желательно изучить взаимодействие двух особенностей; распространенной теоретической установкой является система F <: . Различные исчисления, которые пытаются отразить теоретические свойства объектно-ориентированного программирования, могут быть получены из системы F <: .

Понятие подтипирования родственно лингвистическим понятиям гипонимии и холонимии . Это также связано с концепцией ограниченной количественной оценки в математической логике (см. Логика с сортировкой по порядку ). Подтипирование не следует путать с понятием наследования (класса или объекта) из объектно-ориентированных языков; [3] подтипирование — это отношение между типами (интерфейсами на объектно-ориентированном языке), тогда как наследование — это отношение между реализациями, вытекающее из особенности языка, которая позволяет создавать новые объекты из существующих. В ряде объектно-ориентированных языков подтипирование называется наследованием интерфейса , а наследование — наследованием реализации .

Происхождение [ править ]

Идея подтипирования в языках программирования возникла в 1960-х годах; он был представлен в производных Simula . Первые формальные подходы к подтипированию были предложены Джоном К. Рейнольдсом в 1980 году, который использовал теорию категорий для формализации неявных преобразований , и Лукой Карделли (1985). [4]

Концепция подтипирования приобрела известность (и в некоторых кругах стала синонимом полиморфизма) с массовым внедрением объектно-ориентированного программирования. В этом контексте принцип безопасной замены часто называют принципом замены Лискова в честь Барбары Лисков , которая популяризировала его в программном выступлении на конференции по объектно-ориентированному программированию в 1987 году. Поскольку он должен учитывать изменяемые объекты, идеальное понятие подтипирования Определенный Лисковым и Жаннетт Винг , называемый поведенческим подтипированием , он значительно мощнее того, что можно реализовать в средствах проверки типов . ( см. § Типы функций Подробнее ниже.)

Примеры [ править ]

Пример подтипов: где птица — это супертип, а все остальные — подтипы, обозначенные стрелкой в UML . ​​нотации

Простой практический пример подтипов показан на схеме. Тип «птица» имеет три подтипа «утка», «кукушка» и «страус». Концептуально каждая из них представляет собой разновидность базового типа «птица», которая наследует многие «птичьи» характеристики, но имеет некоторые специфические различия. На этой диаграмме используется нотация UML : стрелки с открытой головкой показывают направление и тип связи между супертипом и его подтипами.

В качестве более практичного примера язык может позволять использовать целочисленные значения везде, где ожидаются значения с плавающей запятой ( Integer <: Float), или он может определять универсальный тип Число как общий супертип целых и действительных чисел. Во втором случае мы имеем только Integer <: Number и Float <: Number, но Integer и Float не являются подтипами друг друга.

Программисты могут воспользоваться преимуществами подтипирования для написания кода более абстрактным образом , чем это было бы возможно без него. Рассмотрим следующий пример:

функция   max   (  x   как   число  ,   y   как   число  )   — это 
     если   x   <   y   , то 
         вернуть   y 
     , иначе 
         вернуть   x 
 end 

Если целое и вещественное являются подтипами Number, и для обоих типов определен оператор сравнения с произвольным числом, то в эту функцию можно передавать значения любого типа. Однако сама возможность реализации такого оператора сильно ограничивает тип Number (например, нельзя сравнивать целое число с комплексным числом), и фактически имеет смысл только сравнение целых чисел с целыми числами и действительных чисел с действительными числами. Переписывание этой функции так, чтобы она принимала только «x» и «y» одного и того же типа, требует ограниченного полиморфизма .

Подведение [ править ]

В теории типов концепция включения [5] используется для определения или оценки того, является ли тип подтипом типа T. S

Тип — это набор значений. Набор можно описать экстенсионально , перечислив все значения, или его можно описать интенсионально, указав принадлежность к множеству с помощью предиката над областью возможных значений. В распространенных языках программирования типы перечислений определяются расширенно путем перечисления значений. Пользовательские типы, такие как записи (структуры, интерфейсы) или классы, определяются намеренно путем явного объявления типа или использования существующего значения, которое кодирует информацию о типе, в качестве прототипа, подлежащего копированию или расширению.

При обсуждении концепции включения набор значений типа обозначается путем написания его имени математическим курсивом: T . Тип, рассматриваемый как предикат над доменом, обозначается жирным шрифтом: T . Обычный символ <: означает «является подтипом», а :> означает «является супертипом». [ нужна цитата ]

  • Тип T включает в себя S, набор значений T , который он определяет, является надмножеством множества S , так что каждый член S также является членом T. если
  • Тип может быть отнесен к более чем одному типу: супертипы S пересекаются в S. точке
  • Если S <: T (и, следовательно, S T ), то T , предикат, описывающий множество T , должен быть частью предиката S (в той же области), который S. определяет
  • Если S включает в себя T , а T включает в себя S , то эти два типа равны (хотя они могут не быть одним и тем же типом, если система типов различает типы по имени).

С точки зрения специфичности информации подтип считается более конкретным, чем любой из его супертипов, поскольку он содержит по крайней мере столько же информации, сколько каждый из них. Это может повысить применимость или релевантность подтипа (количество ситуаций, в которых он может быть принят или введен) по сравнению с его «более общими» супертипами. Недостаток наличия этой более подробной информации заключается в том, что она представляет собой включенный выбор, который снижает распространенность подтипа (количество ситуаций, которые могут его породить или произвести).

В контексте включения определения типов могут быть выражены с использованием нотации Set-builder , которая использует предикат для определения набора. Предикаты могут быть определены в домене (наборе возможных значений) D . Предикаты — это частичные функции, которые сравнивают значения с критериями выбора. Например: «является ли целочисленное значение большим или равным 100 и меньшим 200?». Если значение соответствует критерию, функция возвращает значение. В противном случае значение не выбирается и ничего не возвращается. (Понимание списков — это форма этого шаблона, используемая во многих языках программирования.)

Если есть два предиката, который применяет критерии выбора для типа T и который применяет дополнительные критерии для типа S , тогда можно определить наборы для двух типов:

Предикат применяется наряду как часть составного предиката S определяющего S. , Два предиката объединены , поэтому для выбора значения оба должны быть истинными. Предикат включает предикат T , поэтому S <: T.

Например: существует подсемейство видов кошек под названием Felinae , которое является частью семейства Felidae . Род Felis , к которому принадлежит вид домашних кошек Felis catus , является частью этого подсемейства.

Соединение предикатов выражается здесь посредством применения второго предиката к области значений, соответствующей первому предикату. Если рассматривать как типы, Felis <: Felinae <: Felidae .

Если T включает в себя S ( T :> S ), то процедура, функция или выражение с заданным значением в качестве операнда (значения параметра или термина), следовательно, сможет работать с этим значением как с одним из типов T , поскольку . В приведенном выше примере мы могли ожидать, что функция Subfamily будет применима к значениям всех трех типов Felidae , Felinae и Felis .

Схемы подтипирования [ править ]

Теоретики типов проводят различие между номинальным подтипированием , при котором только типы, объявленные определенным образом, могут быть подтипами друг друга, и структурным подтипированием , при котором структура двух типов определяет, является ли один подтипом другого. Описанное выше объектно-ориентированное подтипирование на основе классов является номинальным; правило структурного подтипирования для объектно-ориентированного языка может гласить, что если объекты типа A могут обрабатывать все сообщения, которые могут обрабатывать объекты типа B (то есть, если они определяют все те же методы ), то A является подтипом B независимо от того, наследует ли один из них другой. Эта так называемая утиная типизация распространена в динамически типизированных объектно-ориентированных языках. Хорошо известны также разумные правила структурного подтипирования для типов, отличных от типов объектов. [ нужна цитата ]

Реализации языков программирования с подтипированием делятся на два общих класса: инклюзивные реализации, в которых представление любого значения типа A также представляет то же значение типа B , если A <: B , и принудительные реализации, в которых значение типа A может быть автоматически преобразован тип B. в Подтипирование, вызванное созданием подклассов в объектно-ориентированном языке, обычно является инклюзивным; Отношения подтипирования, связывающие целые числа и числа с плавающей запятой, которые представляются по-разному, обычно являются принудительными.

Почти во всех системах типов, которые определяют отношение подтипирования, оно рефлексивно (что означает A <: A для любого типа A ) и транзитивно (это означает, что если A <: B и B <: C , то A <: C ). Это делает его предзаказом на типы.

Типы записей [ править ]

Подтипы ширины и глубины [ править ]

Типы записей порождают концепции подтипов по ширине и глубине . Они выражают два разных способа получения записи нового типа, которая допускает те же операции, что и исходный тип записи.

Напомним, что запись представляет собой набор (именованных) полей. Поскольку подтип — это тип, который допускает все операции, разрешенные для исходного типа, подтип записи должен поддерживать те же операции с полями, что и исходный тип.

Один из способов достижения такой поддержки, называемый подтипированием ширины , добавляет в запись больше полей. Более формально, каждое (именованное) поле, появляющееся в супертипе ширины, будет отображаться в подтипе ширины. Таким образом, любая операция, осуществимая над супертипом, будет поддерживаться подтипом.

Второй метод, называемый подтипированием глубины , заменяет различные поля их подтипами. То есть поля подтипа являются подтипами полей супертипа. Поскольку любая операция, поддерживаемая для поля супертипа, поддерживается и для его подтипа, любая операция, допустимая для супертипа записи, поддерживается подтипом записи. Подтип глубины имеет смысл только для неизменяемых записей: например, вы можете присвоить 1,5 полю «x» реальной точки (записи с двумя реальными полями), но вы не можете сделать то же самое с полем «x» целочисленная точка (которая, однако, является глубоким подтипом типа вещественной точки), поскольку 1,5 не является целым числом (см. Дисперсия ).

Подтипирование записей может быть определено в системе F <: , которая сочетает в себе параметрический полиморфизм с подтипированием типов записей и является теоретической основой для многих функциональных языков программирования , поддерживающих обе функции.

Некоторые системы также поддерживают подтипирование помеченных типов непересекающихся объединений (например, алгебраических типов данных ). Правило подтипа ширины обратное: каждый тег, появляющийся в подтипе ширины, должен присутствовать в супертипе ширины.

Типы функций [ править ]

Если T 1 T 2 является типом функции, то его подтипом является любой тип функции S 1 S 2 со свойством, что T 1 <: S 1 и S 2 <: T 2 . Это можно резюмировать, используя следующее правило типизации :

Тип параметра S 1 S 2 называется контравариантным, поскольку для него отношение подтипирования меняется на противоположное, тогда как тип возвращаемого значения является ковариантным . Неформально, это обращение происходит потому, что уточненный тип «более либерален» в типах, которые он принимает, и «более консервативен» в типах, которые он возвращает. Именно это работает в Scala : n -арная функция внутри является классом, который наследует типаж (который можно рассматривать как общий интерфейс в Java -подобных языках), где типы параметров и это тип возвращаемого значения; Знак «-» перед типом означает, что тип является контравариантным, а «+» означает ковариантный.

В языках, допускающих побочные эффекты, как и в большинстве объектно-ориентированных языков, подтипирования обычно недостаточно, чтобы гарантировать, что одну функцию можно безопасно использовать в контексте другой. Работа Лискова в этой области сосредоточилась на поведенческом подтипировании , которое помимо обсуждаемой в этой статье безопасности системы типов также требует, чтобы подтипы сохраняли все инварианты , гарантированные супертипами в некотором контракте . [6] Это определение подтипа обычно неразрешимо , поэтому его нельзя проверить с помощью средства проверки типов .

Подтипирование изменяемых ссылок аналогично обработке значений параметров и возвращаемых значений. Ссылки только для записи (или приемники ) контравариантны, как и значения параметров; Ссылки только для чтения (или источники ) являются ковариантными, как и возвращаемые значения. Изменяемые ссылки, которые действуют как источники и приемники, инвариантны.

Связь с наследованием [ править ]

Подтипирование и наследование являются независимыми (ортогональными) отношениями. Они могут совпадать, но ни один из них не является частным случаем другого. Другими словами, между двумя типами S и T возможны все комбинации подтипирования и наследования:

  1. S не является ни подтипом, ни производным типом T
  2. S является подтипом, но не является производным типом T.
  3. S не является подтипом, а является производным типом T
  4. S является одновременно подтипом и производным типом T

Первый случай иллюстрируется независимыми типами, такими как Boolean и Float.

Второй случай можно проиллюстрировать соотношением между Int32 и Int64. В большинстве объектно-ориентированных языков программирования Int64 не связаны по наследству с Int32. Однако Int32 можно рассматривать как подтип Int64 поскольку любое 32-битное целое значение можно преобразовать в 64-битное целое значение.

Третий случай является следствием подтипирования входной контравариантности функции . Предположим, что суперкласс типа T имеет метод m , возвращающий объект того же типа ( т. е. тип m T T , также обратите внимание, что первый параметр m — это this/self) и тип производного класса S от T. . тип m в S S S. По наследству [ нужна цитата ] Чтобы S был подтипом T, тип m в S должен быть подтипом типа m в T. [ нужна цитата ] другими словами: S S ≤: T T . При применении правила подтипирования функций снизу вверх это означает: S ≤: T и T ≤: S , что возможно только в том случае, если S и T одинаковы. Поскольку наследование является иррефлексивным отношением, S может быть подтипом T. не

Подтипирование и наследование совместимы, когда все унаследованные поля и методы производного типа имеют типы, которые являются подтипами соответствующих полей и методов унаследованного типа. [3]

Принуждение [ править ]

В системах принудительного подтипирования подтипы определяются с помощью неявных функций преобразования типов из подтипа в супертип. Для каждого отношения подтипирования ( S <: T ) любой объект s типа S предоставляется принудительная функция приведения : S → T, и рассматривается как приведение объекта S T ( s ) типа T . Функция приведения может быть определена композицией: если S <: T и T <: U , то s можно рассматривать как объект типа u при составном приведении ( приведение T U приведение S T ). от Приведение типа типа к самому себе T T это тождественная функция id T .

Функции приведения для записей и подтипов непересекающихся объединений могут определяться покомпонентно; в случае записей расширенной ширины приведение типа просто отбрасывает все компоненты, которые не определены в супертипе. Приведение типов для типов функций может быть задано формулой f' ( t ) = coerce S 2 T 2 ( f ( coerce T 1 S 1 ( t ))), что отражает контравариантность значений параметров и ковариацию возвращаемых значений.

Функция приведения однозначно определяется с учетом подтипа и супертипа . Таким образом, когда определены множественные отношения подтипов, необходимо быть осторожным, чтобы гарантировать, что все приведения типов являются последовательными. Например, если целое число, такое как 2 : int, можно привести к числу с плавающей запятой (скажем, 2.0 : float недопустимо ), то приведение 2.1 : float к 2 : int , поскольку составное приведение приводит float float. заданное принуждением int float coerce float int, тогда будет отличаться от идентификатора приведения идентичности float .

См. также [ править ]

Примечания [ править ]

  1. ^ Копстейк, Энн. Реализация грамматик типизированной структуры объектов. Том. 110. Стэнфорд: публикации CSLI, 2002.
  2. ^ Карделли, Лука. Семантика множественного наследования. В книге Г. Кана, Д. МакКуина и Г. Плоткина, редакторов, «Семантика типов данных», том 173 конспектов лекций по информатике, страницы 51–67. Springer-Verlag, 1984. Полная версия в журнале Information and Computation, 76(2/3):138–164, 1988.
  3. ^ Перейти обратно: а б Кук, Хилл и Каннинг, 1990 .
  4. ^ Пирс, гл. 15 нот
  5. ^ Бенджамин К. Пирс, Типы и языки программирования , MIT Press, 2002, 15.1 «Подчинение», с. 181-182
  6. ^ Барбара Лисков, Жаннетт Винг, Поведенческое понятие подтипирования , Транзакции ACM в языках и системах программирования, том 16, выпуск 6 (ноябрь 1994 г.), стр. 1811–1841. Обновленная версия появилась в виде технического отчета CMU: Лисков, Варвара ; Винг, Жаннетт (июль 1999 г.). «Поведенческое подтипирование с использованием инвариантов и ограничений» ( PS ) . Проверено 5 октября 2006 г.

Ссылки [ править ]

Учебники

  • Бенджамин К. Пирс, Типы и языки программирования , MIT Press, 2002, ISBN   0-262-16209-1 , глава 15 (подтипирование типов записей), 19.3 (номинальные и структурные типы и подтипы) и 23.2 (разновидности полиморфизма)
  • К. Шиперски, Д. Гранц, С. Мурер, Компонентное программное обеспечение: за пределами объектно-ориентированного программирования , 2-е изд., Pearson Education, 2002, ISBN   0-201-74572-0 , стр. 93–95 (презентация высокого уровня, предназначенная для пользователей языков программирования)

Статьи

Кук, Уильям Р.; Хилл, Уолтер; Каннинг, Питер С. (1990). Наследование не является подтипом . Учеб. 17-й симпозиум ACM SIGPLAN-SIGACT. по принципам языков программирования (POPL). стр. 125–135. CiteSeerX   10.1.1.102.8635 . дои : 10.1145/96709.96721 . ISBN  0-89791-343-4 .
  • Рейнольдс, Джон К. Использование теории категорий для разработки неявных преобразований и универсальных операторов. В Н. Д. Джонсе, редакторе, «Труды Орхусского семинара по семантически-ориентированной генерации компиляторов», номер 94 в «Конспектах лекций по информатике». Springer-Verlag, январь 1980 г. Также в книге Карла А. Гюнтера и Джона К. Митчелла, редакторов, «Теоретические аспекты объектно-ориентированного программирования: типы, семантика и языковой дизайн» (MIT Press, 1994).

Дальнейшее чтение [ править ]