yegor256-strong-typing-no-types

View the Project on GitHub serge3ling/yegor256-strong-typing-no-types

Строга типізація без типів

(Оригінал цієї статті знаходиться тут.)

10 листопада 2020 р. Москва, Росія.

Автор: Єгор Бугаєнко.

В 1974 році Лісков (Liskov) і Зилес (Zilles) дали визначення мови зі строгою типізацією як такої, в якій “кожного разу, коли об’єкт передається від викликаючої функції до викликуваної, його тип мусить бути сумісним із типом, оголошеним у викликуваній функції”. Строга перевірка типів, поза сумнівом, зменшує кількість помилок, пов’язаних із типами, а це підвищує якість. Однак є питання: чи нам потрібно вказувати типи, щоб отримати строгу типізацію?

Redirected (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!

Що скажете?