Хороша система загального типу


29

Загальноприйнято вважати, що дженерики Java не вдалися в деяких важливих способах. Поєднання марок та меж призвело до деякого серйозно нечитабельного коду.

Однак, переглядаючи інші мови, я справді не можу знайти систему загального типу, якою задоволені програмісти.

Якщо взяти за основу цілі дизайну такої системи типу:

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

Чи є якась мова, яка б це зрозуміла? Якщо я google, єдине, що я бачу, - це скарги на те, як система типів висмоктує мову X. Чи такий тип складності притаманний загальній типізації? Чи варто просто відмовитися від спроби перевірити безпеку типу на 100% під час компіляції?

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

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


2
Що ви маєте на увазі під easy-to-read type declarations? Третій критерій також неоднозначний: наприклад, я можу перетворити індекс масиву поза межами виключень у помилки часу компіляції, не дозволяючи вам індексувати масиви, якщо я не можу обчислити індекс під час компіляції. Також другий критерій виключає підгрупування. Це не обов'язково погано, але ви повинні знати, що ви просите.
Довал


9
@gnat, це, безумовно, не зграя проти Java. Я програмую майже виключно на Java. Моя думка полягає в тому, що в спільноті Java загальновизнано, що Generics є недоліком (не повним збоєм, але, мабуть, частковим), тому задати питання, як вони мали бути реалізовані, є логічним питанням. Чому вони помиляються і чи правильно їх зрозуміли інші? Або насправді неможливо отримати генерики абсолютно правильними?
Пітер

1
Якщо всі просто крали з C #, було б менше скарг. Особливо Java може встигнути наздогнати копіювання. Натомість вони приймають рішення про неповноцінні рішення. Багато питань, які обговорюють комітети дизайну Java, уже вирішені та впроваджені в C #. Вони навіть не виглядають.
usr

2
@emodendroket: Я думаю, що мої дві найбільші скарги на C # generics полягають у тому, що немає можливості застосувати обмеження "супертипу" (наприклад Foo<T> where SiameseCat:T) і що немає можливості мати загальний тип, до якого не можна конвертувати Object. IMHO, .NET отримав би перевагу від сукупних типів, схожих на структури, але ще більше голими. Якби KeyValuePair<TKey,TValue>такий тип був, то на нього IEnumerable<KeyValuePair<SiameseCat,FordFocus>>можна було б подати IEnumerable<KeyValuePair<Animal,Vehicle>>, але лише у тому випадку, якщо тип не міг бути полем.
supercat

Відповіді:


24

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

Однак, навіть якщо ми зосередимось на об’єктно-орієнтованих мовах програмування, зокрема на Java, могла б бути розроблена значно краща система генерики:

  1. Родові типи повинні бути допустимими, де б не були інші типи. Зокрема, якщо Tце параметр типу, наступні вирази повинні складатись без попереджень:

    object instanceof T; 
    T t = (T) object;
    T[] array = new T[1];
    

    Так, для цього потрібні вдосконалення дженериків, як і будь-який інший тип мови.

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

    Future<Provider<Integer>> s;
    Future<Provider<Number>> o = s; 
    

    а не

    Future<? extends Provider<Integer>> s;
    Future<? extends Provider<? extends Number>> o = s;
    
  3. Оскільки родові типи можуть бути досить довгими, нам не потрібно буде надмірно їх вказувати. Тобто ми повинні вміти писати

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (var e : map.values()) {
        for (var list : e.values()) {
            for (var person : list) {
                greet(person);
            }
        }
    }
    

    а не

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (Map<String, List<LanguageDesigner>> e : map.values()) {
        for (List<LanguageDesigner> list : e.values()) {
            for (LanguageDesigner person : list) {
                greet(person);
            }
        }
    }
    
  4. Будь-який тип повинен бути прийнятним як параметр типу, а не лише типи посилань. (Якщо ми можемо мати int[], чому ми не можемо мати List<int>)?

Все це можливо на C #.


1
Чи це також позбудеться самореференційних дженериків? Що робити, якщо я хочу сказати, що порівнянний об'єкт може порівнювати себе з будь-чим одного типу або підкласом? Це можна зробити? Або якщо я напишу метод сортування, який приймає списки із порівнянними об'єктами, всі вони повинні бути порівнянні один з одним. Енум - ще один хороший приклад: Enum <E розширює Enum <E>>. Я не кажу, що система типів повинна це робити, мені просто цікаво, як C # вирішує ці ситуації.
Пітер

1
Узагальнений тип Java 7 та автоматична допомога C ++ допомагають вирішити деякі з цих проблем, але є синтаксичним цукром і не змінюють основні механізми.

@Snowman У типі висновку Java є дуже потворні кутові випадки, як, наприклад, взагалі не працювати з анонімними класами і не знаходити правильних меж для підстановок, коли ви оцінюєте загальний метод як аргумент іншому загальному методу.
Довал

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

34

Використання підтипів створює масу ускладнень при виконанні загального програмування. Якщо ви наполягаєте на використанні мови з підтипами, ви повинні прийняти, що в загальному програмуванні існує певна складна складність, яка є разом із нею. Деякі мови роблять це краще, ніж інші, але ви можете взяти це тільки поки що.

Порівняйте це, наприклад, з дженериками Haskell. Вони досить прості, що, якщо використовувати умовивід типу, ви можете записати правильну загальну функцію випадково . Справді, якщо вказати один тип, компілятор часто говорить собі: «Ну, я був збираюся зробити це родовим, але ви попросили мене зробити це тільки для Інтс, так що завгодно.»

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


1
Дякую за цю відповідь. Ця стаття починається з деяких прикладів Джошуа Блоха про те, де генерика стає надто складною: artima.com/weblogs/viewpost.jsp?thread=222021 . Це різниця в культурі між Java та Haskell, де такі конструкції вважалися б чудовими в Haskell, чи є реальна різниця в типовій системі Haskell, яка уникає подібних ситуацій?
Петро

10
@Peter Haskell не має підтипів, і, як сказав Карл, компілятор може автоматично виводити типи, включаючи обмеження типу "тип aповинен бути якимось цілим числом".
Доваль

Іншими словами коваріація в таких мовах, як Scala.
Пол Дрейпер

14

Було досить багато досліджень щодо поєднання генерики з підтипом, що тривали близько 20 років тому. Мова програмування Thor, розроблений дослідницькою групою Барбари Лісков в MIT, мав поняття "де", які дозволяють вам вказувати вимоги типу, для якого ви параметризуєте. (Це схоже на те, що C ++ намагається зробити з Concepts .)

Документ, що описує дженерики Тора та те, як вони взаємодіють з підтипами Тор, такий: День, М; Gruber, R; Лісков, Б; Майєрс, AC: Підтипи та де пункти: обмеження параметричного поліморфізму , ACM Conf у програмах, орієнтованих на Obj, Sys, Lang та додатки , (OOPSLA-10): 156-158, 1995.

Я вважаю, що вони, у свою чергу, базувались на роботі, яку було зроблено на Смарагді наприкінці 1980-х. (Я не читав цю роботу, але посилання є: Чорний, A; Хатчінсон, N; Лип, Е; Леві, Н; Картер, L: типи розповсюдження та абстрактні типи в Смарагді , _IEEE T. Software Eng., 13 ( 1): 65-76, 1987.

І Тор, і Смарагд були "академічними мовами", тому, ймовірно, вони не отримали достатнього використання, щоб люди справді зрозуміли, чи справді вирішуються які-небудь реальні проблеми. Цікаво прочитати статтю Bjarne Stroustrup про те, чому не вдалося здійснити першу спробу Concepts in C ++: Stroustrup, B: C ++ 0x Рішення "Видалити поняття" , д-р Доббс , 22 липня 2009 р. (Детальна інформація на домашній сторінці Stroustrup . )

Ще один напрямок, який люди, схоже, намагаються - це щось, що називається рисами . Наприклад, мова програмування Mozilla Rust використовує риси. Як я розумію (це може бути абсолютно неправильно), заявляти, що клас задовольняє ознаку, дуже схоже на те, що сказати, що клас реалізує інтерфейс, але ви говорите: "поводиться як", а не "є". Здається, що нові мови програмування Swift Apple використовують аналогічну концепцію протоколів, щоб вказати обмеження параметрів на generics .

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