На прошлой неделе мой друг задал, казалось бы, безобидный вопрос о языке 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
и C
values...
... Пока мы не захотим List[T1 with T2 with T3]
.В этот момент, даже если у нас есть неявные преобразования A => T1
, A => T2
и A => T3
, мы не можем поместить A
в список. Мы могли бы реструктурировать наши неявные преобразования, чтобы буквально обеспечить A => T1 с T2 с T3
, но я никогда не видел, чтобы кто-то делал это раньше, и это похоже на еще одну форму тесной связи.
Итак, мой вопрос, наконец, представляет собой комбинацию нескольких вопросов, которые ранее задавались здесь: «почему следует избегать создания подтипов?» и «преимущества создания подтипов по сравнению с классами типов»... существует ли какая-то объединяющая теория, которая утверждает, что непредикативный полиморфизм и полиморфизм подтипов — это одно и то же? Являются ли имплицитные обращения каким-то тайным детищем любви двоих? И может ли кто-нибудь сформулировать хороший, чистый шаблон для выражения нескольких границ (как в последнем примере выше) в Scala?