Один мой друг представлял на прошлой неделе кажущийся безобидным Scala вопрос языка, на который у меня не было хорошего ответа: есть ли простой способ объявить коллекцию вещей, принадлежащих к некоторому обычному классу. Конечно, в классе Scala нет первоклассного понятия "typeclass", поэтому мы должны думать об этом с точки зрения черт и границ контекста (т.е. Implicits).
Конкретно, учитывая некоторый признак T[_]
, представляющий тип класса, и типы A
, B
и C
, с соответствующими имплицитами в области T[A]
, T[B]
и T[C]
, мы хотим объявить что-то вроде List[T[a] forAll { type a }]
, в которое мы можем безнаказанно сбрасывать экземпляры A
, B
и C
. Это, конечно, не существует в Scala; a в прошлом году обсуждает это более подробно.
Естественный вопрос о последующем: "Как это делает Хаскелл?" Ну, GHC, в частности, имеет расширение системы типов, называемое impredicative polymorphism, описанное в " Boxy Types ". Короче говоря, при задании класса T
можно законно построить список [forall a. T a => a]
. Учитывая объявление этой формы, компилятор выполняет некоторую магию передачи словаря, которая позволяет нам сохранять экземпляры typeclass, соответствующие типам каждого значения в списке во время выполнения.
Вещь, "магия передачи словаря" звучит так же, как "vtables". На объектно-ориентированном языке, таком как Scala, подтипирование является гораздо более простым и естественным механизмом, чем подход "Типы ящиков". Если наши A
, B
и C
все расширяют признак T
, мы можем просто объявить List[T]
и быть счастливыми. Аналогично, как отмечает Майлз в комментарии ниже, если все они расширяют черты T1
, T2
и T3
, тогда я могу использовать List[T1 with T2 with T3]
как эквивалент неактивного Haskell [forall a. (T1 a, T2 a, T3 a) => a]
.
Однако основной недостаток с подтипированием по сравнению с типами типов - это тесная связь: у моих типов A
, B
и C
должно быть реализовано поведение T
. Предположим, что это основной манипулятор, и я не могу использовать подтипирование. Таким образом, средняя точка в Scala - это сутенеры ^ H ^ H ^ H ^ H ^ Himplicit преобразования: при некоторых A => T
, B => T
и C => T
в неявной области я снова вполне счастливо заполняю List[T]
my A
, B
и C
значения...
... Пока мы не хотим List[T1 with T2 with T3]
. В этот момент, даже если мы имеем неявные преобразования A => T1
, A => T2
и A => T3
, мы не можем поместить в список A
. Мы могли бы реструктурировать наши неявные преобразования, чтобы буквально предоставить A => T1 with T2 with T3
, но я никогда не видел, чтобы кто-то делал это раньше, и это похоже на еще одну форму жесткой связи.
Хорошо, поэтому мой вопрос, наконец, - это, наверное, комбинация пары вопросов, которые ранее задавались здесь: , почему бы избежать подтипов? и "преимущества подтипирования по классам" ... существует ли какая-то объединяющая теория, в которой говорится, что импликативный полиморфизм и полиморфизм подтипов являются одними и теми же? Являются ли скрытые преобразования каким-то тайным любовным ребенком этих двух? И может кто-нибудь сформулировать хороший, чистый шаблон для выражения нескольких границ (как в последнем примере выше) в Scala?