Непредикативные типы против старого доброго подтипа

На прошлой неделе мой друг задал, казалось бы, безобидный вопрос о языке Scala, на который у меня не было хорошего ответа: существует ли простой способ объявить набор элементов, принадлежащих некоторому общему классу типов. Конечно, в Scala нет первоклассного понятия «класса типов», поэтому мы должны думать об этом с точки зрения трейтов и границ контекста (т. е. неявных).

Конкретно, учитывая некоторый признак T[_], представляющий класс типов, и типы A, Bи Cс соответствующими имплициты в области видимости T[A], T[B]и T[C], мы хотим объявить что-то вроде List[T[ a] forAll { type a }], в который мы можем безнаказанно бросать экземпляры A, Bи C. Этого, конечно, не существует в Scala; вопрос прошлого годаобсуждает это более подробно.

Естественный последующий вопрос: «Как это делает Haskell?» Что ж, GHC, в частности, имеет расширение системы типов, называемое непредикативным полиморфизмом, описанное в статье "Boxy Types". Короче говоря, имея класс типов T, можно легально построить список [для всех a. Та => а].Учитывая объявление этой формы, компилятор делает некоторую магию передачи словаря, что позволяет нам сохранять экземпляры класса типов, соответствующие типам каждого значения в списке, во время выполнения.

Дело в том, что «магия передачи словаря» очень похожа на «vtables». В объектно-ориентированном языке, таком как Scala, создание подтипов является гораздо более простым и естественным механизмом, чем подход «Boxy Types». Если наши A, Bи Cвсе расширяют трейт T, тогда мы можем просто объявить List[T]и будь счастлив. Точно так же, как отмечает Майлз в комментарии ниже, если все они расширяют черты T1, T2и T3, тогда я могу использовать List[T1 с T2 с T3]как эквивалент непредикативного Haskell [для всех a. (T1 a, T2 a, T3 a) => a].

Однако основным, хорошо известным недостатком подтипов по сравнению с классами типов является тесная связь: мои типы A, Bи Cдолжны иметь свои Tповедение закрепилось. Предположим, что это серьезное препятствие, и я не могуиспользовать подтипы. Таким образом, золотая середина в Scala — это pimps^H^H^H^H^Неявные преобразования: учитывая некоторые A => T, B => Tи C => Tв неявной области видимости, я снова могу с радостью заполнить List[T]моими A, Bи Cvalues...

... Пока мы не захотим List[T1 with T2 with T3].В этот момент, даже если у нас есть неявные преобразования A => T1, A => T2и A => T3, мы не можем поместить Aв список. Мы могли бы реструктурировать наши неявные преобразования, чтобы буквально обеспечить A => T1 с T2 с T3, но я никогда не видел, чтобы кто-то делал это раньше, и это похоже на еще одну форму тесной связи.

Итак, мой вопрос, наконец, представляет собой комбинацию нескольких вопросов, которые ранее задавались здесь: «почему следует избегать создания подтипов?» и «преимущества создания подтипов по сравнению с классами типов»... существует ли какая-то объединяющая теория, которая утверждает, что непредикативный полиморфизм и полиморфизм подтипов — это одно и то же? Являются ли имплицитные обращения каким-то тайным детищем любви двоих? И может ли кто-нибудь сформулировать хороший, чистый шаблон для выражения нескольких границ (как в последнем примере выше) в Scala?

59
задан Community 23 May 2017 в 12:00
поделиться