Тип пересечения
Типовые системы |
---|
Общие понятия |
Основные категории |
Второстепенные категории |
В теории типов тип пересечения может быть присвоен значениям, которым можно присвоить как тип, так и тип пересечения. и тип . Этому значению может быть присвоен тип пересечения. в системе типа перекрестка . [1]
Как правило, если диапазоны значений двух типов перекрываются, то значению, принадлежащему пересечению двух диапазонов, может быть присвоен тип пересечения этих двух типов. Такое значение можно безопасно передать в качестве аргумента функциям, ожидающим любой из двух типов.
Например, в Java класс Boolean
реализует как Serializable
и Comparable
интерфейсы. Поэтому объект типа Boolean
можно безопасно передавать функциям, ожидающим аргумент типа Serializable
и функциям, ожидающим аргумент типа Comparable
.
Типы пересечений — это составные типы данных . Подобно типам продуктов , они используются для присвоения объекту нескольких типов. Однако типы продуктов назначаются кортежам , поэтому каждому элементу кортежа назначается определенный компонент типа продукта. Для сравнения, базовые объекты типов пересечений не обязательно являются составными. Ограниченной формой типов пересечений являются уточняющие типы .
Типы пересечений полезны для описания перегруженных функций . [2] Например, если number => number
это тип функции, принимающей число в качестве аргумента и возвращающей число, и string => string
— это тип функции, принимающей строку в качестве аргумента и возвращающей строку, то пересечение этих двух типов можно использовать для описания (перегруженных) функций, которые выполняют одно или другое, в зависимости от типа входных данных, которые им предоставлены.
Современные языки программирования, включая Ceylon , Flow, Java , Scala , TypeScript и Whiley (см. сравнение языков с типами пересечений ), используют типы пересечений для объединения спецификаций интерфейса и выражения специального полиморфизма . В дополнение к параметрическому полиморфизму типы пересечений могут использоваться, чтобы избежать загрязнения иерархии классов из- за сквозных проблем и сократить шаблонный код , как показано в примере TypeScript ниже.
исследование Теоретико-типовое типов пересечений называется дисциплиной типов пересечений . [3] Примечательно, что завершение программы можно точно охарактеризовать с помощью типов пересечений. [4]
Пример TypeScript [ править ]
TypeScript поддерживает типы пересечений, [5] улучшение выразительности системы типов и уменьшение потенциального размера иерархии классов продемонстрировано следующим образом.
Следующий программный код определяет классы Chicken
, Cow
, и RandomNumberGenerator
что у каждого есть метод produce
возврат объекта любого типа Egg
, Milk
, или number
.
Кроме того, функции eatEgg
и drinkMilk
требуются аргументы типа Egg
и Milk
, соответственно.
class Egg { private kind: "Egg" }
class Milk { private kind: "Milk" }
// produces eggs
class Chicken { produce() { return new Egg(); } }
// produces milk
class Cow { produce() { return new Milk(); } }
// produces a random number
class RandomNumberGenerator { produce() { return Math.random(); } }
// requires an egg
function eatEgg(egg: Egg) {
return "I ate an egg.";
}
// requires milk
function drinkMilk(milk: Milk) {
return "I drank some milk.";
}
Следующий программный код определяет специальную полиморфную функцию animalToFood
который вызывает функцию-член produce
данного объекта animal
.
Функция animalToFood
имеет аннотации двух типов, а именно ((_: Chicken) => Egg)
и ((_: Cow) => Milk)
, подключенный через конструктор типа пересечения &
.
Конкретно, animalToFood
при применении к аргументу типа Chicken
возвращает объект типа type Egg
и при применении к аргументу типа Cow
возвращает объект типа type Milk
.
В идеале, animalToFood
не должно быть применимо к любому объекту, имеющему (возможно, случайно) produce
метод.
// given a chicken, produces an egg; given a cow, produces milk
let animalToFood: ((_: Chicken) => Egg) & ((_: Cow) => Milk) =
function (animal: any) {
return animal.produce();
};
Наконец, следующий программный код демонстрирует безопасное использование приведенных выше определений.
var chicken = new Chicken();
var cow = new Cow();
var randomNumberGenerator = new RandomNumberGenerator();
console.log(chicken.produce()); // Egg { }
console.log(cow.produce()); // Milk { }
console.log(randomNumberGenerator.produce()); //0.2626353555444987
console.log(animalToFood(chicken)); // Egg { }
console.log(animalToFood(cow)); // Milk { }
//console.log(animalToFood(randomNumberGenerator)); // ERROR: Argument of type 'RandomNumberGenerator' is not assignable to parameter of type 'Cow'
console.log(eatEgg(animalToFood(chicken))); // I ate an egg.
//console.log(eatEgg(animalToFood(cow))); // ERROR: Argument of type 'Milk' is not assignable to parameter of type 'Egg'
console.log(drinkMilk(animalToFood(cow))); // I drank some milk.
//console.log(drinkMilk(animalToFood(chicken))); // ERROR: Argument of type 'Egg' is not assignable to parameter of type 'Milk'
Приведенный выше программный код имеет следующие свойства:
- Строки 1–3 создают объекты.
chicken
,cow
, иrandomNumberGenerator
соответствующего типа. - Строки 5–7 выводят для ранее созданных объектов соответствующие результаты (представленные в виде комментариев) при вызове.
produce
. - Строка 9 (соответственно 10) демонстрирует безопасное использование метода.
animalToFood
применяется кchicken
(соответственноcow
). - Если строка 11 не закомментирована, это приведет к ошибке типа во время компиляции. Хотя реализация
animalToFood
мог бы вызватьproduce
методrandomNumberGenerator
, аннотация типаanimalToFood
запрещает это. Это соответствует предполагаемому смыслуanimalToFood
. - Строка 13 (соответственно 15) показывает, что применение
animalToFood
кchicken
(соответственноcow
) приводит к объекту типаEgg
(соответственноMilk
). - Строка 14 (соответственно 16) показывает, что применение
animalToFood
кcow
(соответственноchicken
) не приводит к созданию объекта типаEgg
(соответственноMilk
). Следовательно, если строка 14 (соответственно 16) не закомментирована, это приведет к ошибке типа во время компиляции.
Сравнение с наследованием [ править ]
Приведенный выше минималистский пример можно реализовать с помощью наследования , например, путем наследования классов Chicken
и Cow
из базового класса Animal
.
Однако в более крупных условиях это может оказаться невыгодным.
Введение новых классов в иерархию классов не обязательно оправдано из-за сквозных проблем , а может быть и совершенно невозможно, например, при использовании внешней библиотеки.
Можно представить, что приведенный выше пример можно расширить следующими классами:
- класс
Horse
у которого нетproduce
метод; - класс
Sheep
у которого естьproduce
возврат методаWool
; - класс
Pig
у которого естьproduce
метод, который можно использовать только один раз, возвращаяMeat
.
Для этого могут потребоваться дополнительные классы (или интерфейсы), определяющие, доступен ли метод производства, возвращает ли метод производства продукты питания и можно ли использовать метод производства повторно. В целом это может нарушить иерархию классов.
Сравнение с утиным набором текста [ править ]
Приведенный выше минималистский пример уже показывает, что утиная типизация менее подходит для реализации данного сценария.
В то время как класс RandomNumberGenerator
содержит produce
метод, объект randomNumberGenerator
не должно быть веским аргументом в пользу animalToFood
.
Приведенный выше пример можно реализовать с помощью утиной печати, например, введя новое поле. argumentForAnimalToFood
на занятия Chicken
и Cow
означающий, что объекты соответствующего типа являются действительными аргументами для animalToFood
.
Однако это не только увеличит размер соответствующих классов (особенно с введением большего количества методов, подобных animalToFood
), но также является нелокальным подходом по отношению к animalToFood
.
Сравнение с перегрузкой функций [ править ]
Приведенный выше пример можно реализовать с помощью перегрузки функции , например, реализовав два метода. animalToFood(animal: Chicken): Egg
и animalToFood(animal: Cow): Milk
.
В TypeScript такое решение практически идентично приведенному примеру.
Другие языки программирования, такие как Java , требуют отдельных реализаций перегруженного метода.
Это может привести либо к дублированию кода , либо к шаблонному коду .
Сравнение с шаблоном посещений [ править ]
Приведенный выше пример можно реализовать с помощью шаблона посетителя .
Для этого потребуется, чтобы каждый класс животных реализовал accept
метод, принимающий объект, реализующий интерфейс AnimalVisitor
(добавление нелокального шаблонного кода ).
Функция animalToFood
будет реализовано как visit
способ реализации AnimalVisitor
.
К сожалению, связь между типом входа ( Chicken
или Cow
) и тип результата ( Egg
или Milk
) было бы трудно представить.
Ограничения [ править ]
С одной стороны, типы пересечений можно использовать для локального аннотирования различных типов функции без введения новых классов (или интерфейсов) в иерархию классов. С другой стороны, этот подход требует явного указания всех возможных типов аргументов и типов результатов. Если поведение функции можно точно задать с помощью унифицированного интерфейса, параметрического полиморфизма или утиной типизации , то многословный характер типов пересечений невыгоден. Следовательно, типы пересечений следует рассматривать как дополнение к существующим методам спецификации.
Зависимый тип пересечения [ править ]
Зависимый тип пересечения , обозначаемый , является зависимым типом , в котором тип может зависеть от термина переменная . [6] В частности, если термин имеет зависимый тип пересечения , то срок имеет оба типа и тип , где это тип, который получается в результате замены всех вхождений термина переменная в по сроку .
Пример Scala [ править ]
Scala поддерживает объявления типов. [7] как члены объекта. Это позволяет типу члена объекта зависеть от значения другого члена, что называется типом, зависящим от пути . [8]
Например, следующий текст программы определяет особенность Scala. Witness
, который можно использовать для реализации шаблона Singleton . [9]
trait Witness {
type T
val value: T {}
}
Вышеуказанная черта Witness
заявляет член T
, которому можно присвоить тип в качестве значения, а члену value
, которому можно присвоить значение типа T
.
Следующий текст программы определяет объект booleanWitness
как пример вышеуказанной черты Witness
.
Объект booleanWitness
определяет тип T
как Boolean
и ценность value
как true
.
Например, выполнение System.out.println(booleanWitness.value)
принты true
на консоли.
object booleanWitness extends Witness {
type T = Boolean
val value = true
}
Позволять быть типом (в частности, типом записи ) объектов, имеющих член типа .
В приведенном выше примере объект booleanWitness
может быть назначен зависимый тип пересечения .
Аргументация следующая. Объект booleanWitness
имеет члена T
которому присвоен тип Boolean
как его ценность.
С Boolean
это тип, объект booleanWitness
имеет тип .
Кроме того, объект booleanWitness
имеет члена value
которому присвоено значение true
типа Boolean
.
Поскольку значение booleanWitness.T
является Boolean
, объект booleanWitness
имеет тип .
В целом объект booleanWitness
имеет тип пересечения .
Поэтому, представляя ссылку на себя как зависимость, объект booleanWitness
имеет зависимый тип пересечения .
Альтернативно, приведенный выше минималистичный пример можно описать с помощью зависимых типов записей . [10] По сравнению с типами зависимых пересечений, типы зависимых записей представляют собой строго более специализированную концепцию теории типов. [6]
Пересечение типового семейства [ править ]
Пересечение семейства типов , обозначаемое , является зависимым типом , в котором тип может зависеть от термина переменная . В частности, если термин имеет тип , то для каждого члена типа , термин имеет тип . Это понятие еще называют неявным типом Пи , [11] отмечая, что аргумент не сохраняется на уровне срока.
Сравнение языков по типам пересечений [ править ]
Язык | Активно развивается | Парадигма(ы) | Статус | Функции |
---|---|---|---|---|
С# | Да [12] | В стадии обсуждения [13] | Кроме того, параметры универсального типа могут иметь ограничения, которые требуют, чтобы их (мономорфизированные) аргументы типа реализовывали несколько интерфейсов, после чего тип времени выполнения, представленный параметром универсального типа, становится типом пересечения всех перечисленных интерфейсов. | |
Цейлон | Нет [14] | Поддерживается [15] |
| |
Ф# | Да [16] | В стадии обсуждения [17] | ? | |
Поток | Да [18] | Поддерживается [19] |
| |
Форсайт | Нет | Поддерживается [20] |
| |
Ява | Да [21] | Поддерживается [22] |
| |
PHP | Да [23] | Поддерживается [24] |
| |
Скала | Да [25] | Поддерживается [26] [27] |
| |
Машинопись | Да [28] | Поддерживается [5] |
| |
Пока | Да [29] | Поддерживается [30] | ? |
Ссылки [ править ]
- ^ Барендрегт, Хенк; Коппо, Марио; Дезани-Чианкаглини, Марианджола (1983). «Фильтр лямбда-модели и полнота присвоения типа». Журнал символической логики . 48 (4): 931–940. дои : 10.2307/2273659 . JSTOR 2273659 . S2CID 45660117 .
- ^ Палсберг, Йенс (2012). «Перегрузка NP-завершена». Логика и семантика программ . Конспекты лекций по информатике. Том. 7230. стр. 204–218. дои : 10.1007/978-3-642-29485-3_13 . ISBN 978-3-642-29484-6 .
- ^ Хенк Барендрегт; Уил Деккерс; Ричард Стэтман (20 июня 2013 г.). Лямбда-исчисление с типами . Издательство Кембриджского университета. стр. 1–. ISBN 978-0-521-76614-2 .
- ^ Гилезан, Сильвия (1996). «Сильная нормализация и типизация с типами пересечений» . Журнал формальной логики Нотр-Дама . 37 (1): 44–52. дои : 10.1305/ndjfl/1040067315 .
- ↑ Перейти обратно: Перейти обратно: а б «Типы пересечений в TypeScript» . Проверено 1 августа 2019 г.
- ↑ Перейти обратно: Перейти обратно: а б Копылов, Алексей (2003). «Зависимое пересечение: новый способ определения записей в теории типов». 18-й симпозиум IEEE по логике в информатике . LICS 2003. Компьютерное общество IEEE. стр. 86–95. CiteSeerX 10.1.1.89.4223 . дои : 10.1109/LICS.2003.1210048 .
- ^ «Объявления типов в Scala» . Проверено 15 августа 2019 г.
- ^ Амин, Нада; Грюттер, Сэмюэл; Одерский, Мартин; Ромпф, Тиарк; Стуки, Сандро (2016). «Сущность зависимых типов объектов». Список успехов, которые могут изменить мир (PDF) . Конспекты лекций по информатике. Том. 9600. Спрингер. стр. 249–272. дои : 10.1007/978-3-319-30936-1_14 . ISBN 978-3-319-30935-4 .
- ^ «Синглетоны в бесформенной библиотеке Scala» . Гитхаб . Проверено 15 августа 2019 г.
- ^ Поллак, Роберт (2000). «Зависимо типизированные записи для представления математической структуры». Доказательство теорем в логике высшего порядка, 13-я Международная конференция . TPHOLs 2000. Спрингер. стр. 462–479. дои : 10.1007/3-540-44659-1_29 .
- ^ Стамп, Аарон (2018). «От реализуемости к индукции через зависимое пересечение» . Анналы чистой и прикладной логики . 169 (7): 637–655. дои : 10.1016/j.apal.2018.03.002 .
- ^ «Руководство по C#» . Проверено 8 августа 2019 г.
- ^ «Обсуждение: типы объединения и пересечения в C Sharp» . Гитхаб . Проверено 8 августа 2019 г.
- ^ «Затмение Цейлон™» . 19 июля 2017 года . Проверено 16 августа 2023 г.
- ^ «Типы перекрестков на Цейлоне» . 19 июля 2017 года . Проверено 8 августа 2019 г.
- ^ «Фонд программного обеспечения F#» . Проверено 8 августа 2019 г.
- ^ «Добавить типы пересечений к F Sharp» . Гитхаб . Проверено 8 августа 2019 г.
- ^ «Flow: средство проверки статического типа для JavaScript» . Проверено 8 августа 2019 г.
- ^ «Синтаксис типа пересечения в потоке» . Проверено 8 августа 2019 г.
- ^ Рейнольдс, JC (1988). Предварительный проект языка программирования Форсайт.
- ^ «Программное обеспечение Java» . Проверено 8 августа 2019 г.
- ^ «Тип пересечения (Java SE 12 и JDK 12)» . Проверено 1 августа 2019 г.
- ^ «php.net» .
- ^ «PHP.Watch — PHP 8.1: Типы пересечений» .
- ^ «Язык программирования Scala» . Проверено 8 августа 2019 г.
- ^ «Составные типы в Scala» . Проверено 1 августа 2019 г.
- ^ «Типы пересечений в Дотти» . Проверено 1 августа 2019 г.
- ^ «TypeScript — масштабируемый JavaScript» . Проверено 1 августа 2019 г.
- ^ «Whiley: язык программирования с открытым исходным кодом и расширенной статической проверкой» . Проверено 1 августа 2019 г.
- ^ «Спецификация языка Whaley» (PDF) . Проверено 1 августа 2019 г.