Структурна типізація
Структурна система типів (або система типів, заснована на властивостях) — це один із головних класів систем типів, у якому сумісність і еквівалентність типів визначаються фактичною структурою або визначенням типу, а не іншими характеристиками, такими як ім'я або місце оголошення. Структурні системи використовуються для визначення, чи є типи еквівалентними і чи є один тип підтипом іншого. Це контрастує з номінативними системами, де порівняння базуються на назвах типів або явних оголошеннях, і типізацією качки, в якій перевіряється лише та частина структури, що використовується під час виконання для сумісності.
У структурній типізації елемент вважається сумісним з іншим, якщо для кожної характеристики у типі другого елемента існує відповідна ідентична характеристика у типі першого елемента. Деякі мови можуть відрізнятися деталями, наприклад, чи повинні характеристики збігатися за назвою. Це визначення не є симетричним і включає сумісність підтипів. Два типи вважаються ідентичними, якщо кожен з них сумісний з іншим.
Наприклад, OCaml використовує структурну типізацію методів для сумісності типів об'єктів. Go використовує структурну типізацію методів для визначення сумісності типу з інтерфейсом. Функції шаблонів у C++ демонструють структурну типізацію для аргументів типу. Haxe використовує структурну типізацію, але класи не мають структурного підтипування.
У мовах, що підтримують поліморфізм підтипів, може бути сформована подібна дихотомія, заснована на тому, як визначається відношення підтипу. Один тип є підтипом іншого, якщо і лише якщо він містить усі характеристики базового типу або їх підтипи. Підтип може містити додаткові характеристики, такі як члени, яких немає в базовому типі, або сильніші інваріанти.
Існує відмінність між структурною заміною для поліморфізму з виведенням типів і без нього. Деякі мови, такі як Haskell, не замінюють структуру у випадку, коли очікуваний тип оголошено (тобто не виведений), наприклад, заміна виконується лише для функцій, які є поліморфними на основі сигнатури через виведення типів.[1] Таким чином, неможливо випадково зробити підтип для типу, що не виводиться, хоча може залишитися можливість забезпечити явне перетворення до невиведеного типу, яке викликається неявно.
Структурне підтипування вважається більш гнучким, ніж номінативне підтипування, оскільки воно дозволяє створювати ad hoc типи та інтерфейси; зокрема, воно дозволяє створювати тип, що є супертипом існуючого типу, без модифікації визначення останнього. Однак це може бути небажаним, коли програміст хоче створити закриті абстракції.
Недолік структурної типізації порівняно з номінативною полягає в тому, що два окремо визначені типи, призначені для різних цілей, але випадково мають однакові властивості (наприклад, обидва складаються з пари цілих чисел), можуть вважатися однаковими типами системою типів лише тому, що вони мають ідентичну структуру. Один зі способів уникнути цього — створювати окремий алгебраїчний тип даних для кожного випадку використання.
У 1990 році Кук та інші довели, що успадкування не є підтипуванням у мовах з об'єктно-орієнтованою структурною типізацією.[2]
Перевірка сумісності двох типів на основі структурної типізації є нетривіальною операцією, наприклад, вимагає підтримки стека раніше перевірених типів.[3]
Об'єкти в OCaml мають структурну типізацію за іменами та типами їхніх методів.
Об'єкти можуть бути створені безпосередньо (негайні об'єкти) без необхідності проходження через номінативний клас. Класи слугують лише функціями для створення об'єктів.
# let x =
object
val mutable x = 5
method get_x = x
method set_x y = x <- y
end;;
val x : < get_x : int; set_x : int -> unit > = <obj>
Тут інтерактивне середовище виконання OCaml виводить тип об'єкта для зручності. Його тип (< get_x : int; set_x : int -> unit >
) визначається лише його методами. Іншими словами, тип x визначається типами методів "get_x : int" і "set_x : int -> unit", а не за якимось ім'ям.[4]
Для визначення іншого об'єкта, який має ті ж методи і типи методів:
# let y =
object
method get_x = 2
method set_x y = Printf.printf "%d\n" y
end;;
val y : < get_x : int; set_x : int -> unit > = <obj>
OCaml вважає їх одним і тим самим типом. Наприклад, оператор рівності типізований так, щоб приймати лише два значення одного і того ж типу:
# x = y;;
- : bool = false
Отже, вони повинні бути одного типу, інакше це навіть не пройшло б перевірку типів. Це показує, що еквівалентність типів є структурною.
Можна визначити функцію, яка викликає метод:
# let set_to_10 a = a#set_x 10;;
val set_to_10 : < set_x : int -> 'a; .. > -> 'a = <fun>
Виведений тип для першого аргументу (< set_x : int -> 'a; .. >
) цікавий. ..
означає, що перший аргумент може бути будь-яким об'єктом, який має метод "set_x", що приймає ціле число як аргумент.
Отже, його можна використовувати на об'єкті x
:
# set_to_10 x;;
- : unit = ()
Можна створити інший об'єкт, який має такий самий метод і тип методу; інші методи не мають значення:
# let z =
object
method blahblah = 2.5
method set_x y = Printf.printf "%d\n" y
end;;
val z : < blahblah : float; set_x : int -> unit > = <obj>
Функція "set_to_10" також працює для нього:
# set_to_10 z;;
10
- : unit = ()
Це показує, що сумісність для викликів методів визначається структурою.
Визначимо синонім типу для об'єктів, що мають лише метод "get_x" і не мають інших методів:
# type simpler_obj = < get_x : int >;;
type simpler_obj = < get_x : int >
Об'єкт x
не є цього типу; але структурно x
є підтипом цього типу, оскільки x
містить надмножину його методів. Тому x
можна привести до цього типу:
# (x :> simpler_obj);;
- : simpler_obj = <obj>
# (x :> simpler_obj)#get_x;;
- : int = 10
Але об'єкт z
ні, оскільки він не є структурним підтипом:
# (z :> simpler_obj);; Цей вираз не можна привести до типу simpler_obj = < get_x : int >; його тип < blahblah : float; set_x : int -> unit >, але він використовується з типом < get_x : int; .. > У першого типу об'єкта немає методу get_x
Це показує, що сумісність для розширювальних приведень є структурною.
- ↑ Signature-based polymorphism.
- ↑ Кук, В.Р.; Гілл, В.Л.; Кеннінг, П.С. (Січень 1990). Inheritance is not subtyping. Proceedings of the 17th ACM SIGPLAN-SIGACT symposium on Principles of programming languages - POPL '90. Сан-Франциско, Каліфорнія. с. 125—135. doi:10.1145/96709.96721. ISBN 978-0897913430. S2CID 8225906.
- ↑ Type compatibility: name vs structural equivalence.
- ↑ Object types.
- Pierce, Benjamin C. (2002). 19.3. Types and Programming Languages. MIT Press. ISBN 978-0-262-16209-8.