Назва техніки для виведення аргументів типу параметра типу?


9

Налаштування: Припустимо, у нас є тип, Iteratorякий називається, який має параметр типу Element:

interface Iterator<Element> {}

Тоді у нас є інтерфейс, Iterableякий має один метод, який поверне an Iterator.

// T has an upper bound of Iterator
interface Iterable<T: Iterator> {
    getIterator(): T
}

Проблема в Iteratorтому, що є загальним, полягає в тому, що ми повинні надавати йому аргументи типу.

Однією з ідей для вирішення цього є "висновок" типу ітератора. Наступний псевдокод виражає думку про те, що існує змінна тип, Elementяка вважається аргументом типу Iterator:

interface <Element> Iterable<T: Iterator<Element>> {
    getIterator(): T
}

І тоді ми використовуємо його десь так:

class Vec<Element> implements Iterable<VecIterator<Element>> {/*...*/}

Це визначення Iterableне використовує Elementніде більше у своєму визначенні, але мій реальний варіант використання. Окремі функції, які використовують, Iterableтакож повинні мати можливість обмежувати свої параметри, щоб прийняти Iterables, які повертають лише певні види ітераторів, наприклад, двонаправлений ітератор, через що ітератор, що повертається, параметризується замість просто типу елемента.


Запитання:

  • Чи існує усталене ім'я цих змінних виводу? Що з технікою в цілому? Якщо не знати конкретної номенклатури, це ускладнює пошук цих прикладів у дикій природі або дізнання про особливості мови.
  • Не всі мови з генеричними мовами мають цю техніку; Чи є назви подібних прийомів у цих мовах?

1
Чи можете ви показати якийсь код, який не складається, який ви хочете скласти? Я думаю, що це зробить питання зрозумілішим.
Підмітання

1
Ви також можете вибрати одну мову (або сказати, якою мовою ви користуєтесь та що означає синтаксис). Очевидно, це не, наприклад, C #. В Інтернеті є багато інформації про "умовиводи типу", але я не впевнений, що вона застосовується тут.

5
Я реалізую дженерики мовою, не намагаюсь отримати код будь-якою мовою для компіляції. Це також питання щодо найменування та дизайну. Ось чому це дещо агностично. Без знання термінів ускладнює пошук прикладів та документації існуючими мовами. Звичайно, це не унікальна проблема?
Леві Моррісон

Відповіді:


2

Я не знаю, чи існує конкретний термін для цієї проблеми, але є три загальні класи рішення:

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

І звичайно рішення за замовчуванням: продовжуйте правопис усіх цих параметрів.

Уникайте типів бетону.

Ви визначили Iterableінтерфейс як:

interface <Element> Iterable<T: Iterator<Element>> {
    getIterator(): T
}

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

Однак якщо Iterator<E>це динамічно розсилається інтерфейс, то знати конкретний тип не потрібно. Це, наприклад, рішення, яке використовує Java. Потім інтерфейс буде записаний як:

interface Iterable<Element> {
    getIterator(): Iterator<Element>
}

Цікавим варіантом цього є impl Traitсинтаксис Руста, який дозволяє оголосити функцію абстрактним типом повернення, але знаючи, що конкретний тип буде відомий на сайті виклику (таким чином, дозволяючи оптимізувати). Це поводиться аналогічно параметру неявного типу.

Дозволити параметри типу заповнювача.

IterableІнтерфейс не потрібно знати про тип елемента, так що можна було б написати це як:

interface Iterable<T: Iterator<_>> {
    getIterator(): T
}

Там, де T: Iterator<_>виражено обмеження, "T - це будь-який ітератор, незалежно від типу елемента". Більш жорстко ми можемо це висловити так: "існує такий тип, Elementщо Tє Iterator<Element>", не знаючи жодного конкретного типу Element. Це означає, що вираз типу Iterator<_>не описує фактичний тип і може бути використаний лише як обмеження типу.

Використовуйте сім'ї типів / асоційовані типи.

Наприклад, у C ++, тип може мати членів типу. Це зазвичай використовується у всій стандартній бібліотеці, наприклад std::vector::value_type. Це насправді не вирішує проблему параметрів типу у всіх сценаріях, але оскільки тип може посилатися на інші типи, параметр одного типу може описувати ціле сімейство споріднених типів.

Давайте визначимося:

interface Iterator {
  type ElementType
  fn next(): ElementType
}

interface Iterable {
  type IteratorType: Iterator
  fn getIterator(): IteratorType
}

Тоді:

class Vec<Element> implement Iterable {
  type IteratorType = VecIterator<Element>
  fn getIterator(): IteratorType { ... }
}

class VecIterator<T> implements Iterator {
  type ElementType = T
  fn next(): ElementType { ... }
}

Це виглядає дуже гнучко, але зауважте, що це може ускладнити вираження обмежень типу. Наприклад, як написано Iterable, не застосовується жоден тип ітераторних елементів, і ми, можливо, захочемо оголосити interface Iterator<T>замість цього. А зараз ви маєте справу з досить складним численням. Дуже легко випадково зробити таку систему типу нерозбірливою (а може, вона вже є?).

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

interface Iterable<T: Iterator<_>, Element = T::Element> {
  ...
}

Однак це лише особливість ергономіки мови і не робить мову більш потужною.


Системи типу складні, тому добре поглянути на те, що працює, а що не працює на інших мовах.

Наприклад, прочитайте розділ « Розширені риси» у «Книзі іржі», де обговорюються пов’язані типи. Але зауважте, що деякі моменти на користь асоційованих типів замість генеричних застосовуються лише там, оскільки мова не містить підтипів, і кожна ознака може бути реалізована максимум один раз на тип. Тобто риси іржі не є інтерфейсами, подібними до Java.

Інші цікаві системи типу включають Haskell з різними мовними розширеннями. Модулі / функтори OCaml є порівняно простою версією типів сімейства типів, не переплітаючи їх безпосередньо з об'єктами або параметризованими типами. Java примітний обмеженнями в своїй системі типів, наприклад, дженерики зі стиранням типу та відсутність генеричних даних щодо типів значень. C # дуже схожий на Java, але вдається уникнути більшості цих обмежень за рахунок підвищеної складності впровадження. Scala намагається інтегрувати загальну мову C # -style із типовими класами Haskell на вершині платформи Java. Оманливо прості шаблони C ++ добре вивчені, але на відміну від більшості реалізованих дженериків.

Також варто переглянути стандартні бібліотеки цих мов (особливо стандартні колекції бібліотек, такі як списки або хеш-таблиці), щоб побачити, які шаблони зазвичай використовуються. Наприклад, C ++ має складну систему різних можливостей ітератора, і Scala кодує дрібнозернисті можливості збору як риси. Інтерфейси стандартної бібліотеки Java іноді не є звуковими, наприклад Iterator#remove(), але можуть використовувати вкладені класи як певний тип асоційованого типу (наприклад Map.Entry).

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