Автоматичне пригнічення шляхом надання типу


11

У java ви повинні явно подавати заявки, щоб скинути змінну

public class Fruit{}  // parent class
public class Apple extends Fruit{}  // child class

public static void main(String args[]) {
    // An implicit upcast
    Fruit parent = new Apple();
    // An explicit downcast to Apple
    Apple child = (Apple)parent;
}

Чи є якась причина цієї вимоги, окрім того, що java не робить ніякого виводу типу?

Чи є якісь "готчі" із впровадженням автоматичного зриву новою мовою?

Наприклад:

Apple child = parent; // no cast required 

Ви говорите, що якщо компілятор може зробити висновок про те, що об'єкт, який підлягає зниженню, завжди є правильного типу, ви повинні мати можливість явно не висловлювати спад? Але тоді, залежно від версії компілятора і того, наскільки типовий висновок може робити деякі програми, може бути недійсним ... помилки в inferencer типу можуть запобігти написанню дійсних програм тощо. Не має сенсу вводити якісь спеціальні Якщо це залежить від безлічі факторів, користувачеві не було б інтуїтивно зрозуміло, якби їм потрібно було постійно додавати чи видаляти касти без будь-якої причини під час кожного випуску.
Бакуріу

Відповіді:


24

Оновлення завжди вдається.

Даунсинг може призвести до помилки виконання, коли тип часу виконання об'єкта не є підтипом типу, який використовується у складі групи.

Оскільки друга є небезпечною операцією, більшість набраних мов програмування вимагають від програміста прямо запитувати її. По суті, програміст каже компілятору "повір мені, я знаю краще - це буде нормально під час виконання".

Що стосується систем типу, оновлення покладають тягар доказування на компілятор (який повинен перевіряти його статично), а downcast накладає тягар доказу на програміста (який повинен над цим добре подумати).

Можна стверджувати, що належним чином розроблена мова програмування забороняє повністю знищувати дані або надавати альтернативи безпечної трансляції, наприклад повернення необов'язкового типу Option<T>. Однак багато поширених мов обрали простіший і прагматичніший підхід - просто повернутись Tі виправити помилку в іншому випадку.

У вашому конкретному прикладі компілятор міг бути розроблений для того, щоб зробити висновок, що parentнасправді є Appleпростим статичним аналізом, і дозволити неявний склад. Однак загалом проблема не вирішена, тому ми не можемо очікувати, що компілятор виконає занадто багато магії.


1
Для прикладу, прикладом мови, яка переходить на необов'язкові, є Rust, але це тому, що вона не має справжнього успадкування, а лише "будь-якого типу" .
Кролтан

3

Зазвичай придушення - це те, що ти робиш, коли статично відомі знання, які компілятор має про тип чогось, менш специфічні, ніж те, що ти знаєш (або принаймні сподівається).

У таких ситуаціях, як ваш приклад, об'єкт був створений як, Appleа потім це знання було викинуто шляхом зберігання референції у змінній типу Fruit. Тоді ви хочете знову використовувати те саме, що і раніше Apple.

Оскільки інформація була викинута лише "локально", то, звичайно, компілятор міг підтримувати знання, які parentє насправді такими Apple, хоча його заявлений тип є Fruit.

Але зазвичай цього ніхто не робить. Якщо ви хочете створити Appleі використовувати його в якості Apple, ви зберігаєте його в Appleзмінній, а не в Fruitодній.

Коли у вас є Fruitі хочете використовувати його як Appleзвичайно, це означає, що ви отримали Fruitпевні засоби, які, як правило, можуть повернути будь-який вид Fruit, але в цьому випадку ви знаєте, що це було Apple. Майже завжди ви його не просто сконструювали, вам передали якийсь інший код.

Очевидний приклад - якщо у мене є parseFruitфункція, яка може перетворити рядки, такі як "яблуко", "апельсин", "лимон" тощо, у відповідний підклас; як правило , все , що ми (і компілятор) можемо знати про цю функцію є те , що вона повертає яке - то Fruit, але якщо я називаю parseFruit("apple")то я знаю , що збираюся називаємо Appleі , можливо , захочете використовувати Appleметоди, так що я міг би зворотне приведення.

Знову ж таки досить розумний компілятор міг би це зрозуміти, включивши вихідний код для parseFruit, оскільки я називаю це константою (якщо це не в іншому модулі, і у нас є окрема компіляція, як у Java). Але вам слід легко побачити, як складніші приклади, що включають динамічну інформацію, можуть стати складнішими (або навіть неможливими!) Для компілятора для перевірки.

У реалістичному зниженні коду, як правило, трапляється, коли компілятор не міг перевірити, чи є вихідний канал безпечним, використовуючи загальні методи, а не в таких простих випадках, як одразу після оновлення, викидання тієї ж інформації, яку ми намагаємося повернути за допомогою скидання.


3

Справа в тому, де ви хочете провести лінію. Ви можете розробити мову, яка буде виявляти дійсність неявного пониження:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Тепер витягнемо метод:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Ми все ще хороші. Статичний аналіз набагато складніше, але все-таки можливий.

Але проблема з'являється в ту ж саму секунду, яку хтось додає:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

Тепер, де ви хочете підняти помилку? Це створює дилему, можна сказати, що проблема є Apple child = parent;, але це може бути спростовано "Але це працювало раніше". З іншого боку, додавання eat(trouble);викликало проблему ", але вся суть поліморфізму полягає в тому, щоб дозволити саме це.

У цьому випадку ви можете виконати якусь роботу програміста, але ви не можете зробити це все так, як. Чим далі ви приймете це перед тим, як відмовитися, тим складніше буде пояснити, що пішло не так. Отже, краще зупинитись якнайшвидше, відповідно до принципу повідомлення про помилки на ранньому етапі.

До речі, в Яві низхідний стан, який ви описали, насправді не є змушеним. Це загальний акторський склад, який також може кидати яблука до апельсинів. Отже, технічно кажучи, ідея @ chi вже є, в Java немає жодних понижень, лише "Everycasts". Було б сенс розробити спеціалізований оператор пониження, який видасть помилку компіляції, коли її тип результату неможливо знайти нижче за типом аргументу. Було б хорошою ідеєю зробити "Everycast" набагато складнішим у використанні, щоб відмовити програмістів від його використання без дуже вагомих причин. XXXXX_cast<type>(argument)Синтаксис C ++ приходить на думку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.