View the Project on GitHub serge3ling/yegor256-strong-typing-no-types
(Оригінал цієї статті знаходиться тут.)
10 листопада 2020 р. Москва, Росія.
Автор: Єгор Бугаєнко.
В 1974 році Лісков (Liskov) і Зилес (Zilles) дали визначення мови зі строгою типізацією як такої, в якій “кожного разу, коли об’єкт передається від викликаючої функції до викликуваної, його тип мусить бути сумісним із типом, оголошеним у викликуваній функції”. Строга перевірка типів, поза сумнівом, зменшує кількість помилок, пов’язаних із типами, а це підвищує якість. Однак є питання: чи нам потрібно вказувати типи, щоб отримати строгу типізацію?
(Image :copyright: Redirected (2014) by Emilis Velyvis)
Наприклад, тут ми очікуємо, що нам передадуть примірник Java-інтерфейсу Book
:
void print(Book b) {
System.out.printf(
"The ISBN is: %s%n", b.isbn()
);
}
Тип Book
може виглядати так:
interface Book {
String isbn();
}
Якщо об’єкт, який не імплементує інтерфейсу Book
, передається в метод print()
, компілятор викине помилку про неузгодження типів (type mismatch). Програмісту важко зробити помилку і передати об’єкт типу, скажімо, Car
в метод print()
. І все-таки це можливо при використанні динамічного приведення типів:
Car car = new Car("Mercedes-Benz G63");
print(Book.class.cast(car)); // Отут!
Цей код компілюється без помилок, але в час виконання ми отримаємо виняток ClassCastException
, бо неможливо привести Car
до типу Book
.
Краса строгої типізації в тому, що вона запобігає помилкам. Але вона також ускладнює код: спочатку ви маєте створити типи, ви маєте оголосити їх у всіх своїх функціях, вам потрібне приведення типів, а його важко зневадити, і так далі. Захисники слабкої типізації дуже на це скаржаться і винаходять мови, подібні до Ruby, наприклад:
def print(b)
puts(format("This is ISBN: %s", b.isbn))
end
Тут функція print()
не очікує якогось визначеного типу змінної b
. Що їй не дай — все добре. Потім, коли настає час викликати .isbn
, середовище виконання перевіряє, чи b
має такий метод. Якщо має, все в порядку; якщо ні — видається помилка часу виконання NoMethodError.
І ніби все добре.
Але є ідея: а якби ми поєднали простоту і стислість динамічної типізації з безпечністю строгої типізації, викинувши типи зовсім і дозволивши компілятору самому вгадати інформацію про типи з коду, який вживає відповідні об’єкти? Ось наш код знову:
void print(Book b) {
System.out.printf(
"The ISBN is: %s%n", b.isbn()
);
}
Подумайте над таким: в час компіляції вже майже очевидно, що b
мусить мати щонайменше один метод — метод isbn()
. Нема потреби вимагати від програміста визначити тип Book
і явно вказати в сигнатурі методу print()
, що ми дозволяємо тільки “книжкові” об’єкти: це легко вгадати, побачивши тіло методу print()
! Компілятор може поглянути на всі твердження в методі print()
і чітко зрозуміти, що саме має бути зроблено з об’єктом b
. Цих відомостей повинно вистачити, щоб уявити “тип” об’єкту на вході. Нема потреби просити програміста вказати це явно і додати п’ять рядків коду в новому файлі для визначення типу Book
. Компілятор може це зробити замість нас.
Звичайно, щоб це впровадити, ми мусимо заборонити будь-яке приведення типів, а це неможливо в Java, C++, C# та інших псевдо-об’єктно-орієнтованих мовах. Але це можливо в EO!
Що скажете?