Ответ 1
Проблемы, связанные с тем, как Generic
и TypeClass
реализованы, и что они делают, достаточно различны, что, вероятно, они заслуживают отдельных вопросов, поэтому я буду придерживаться Generic
здесь.
Generic
обеспечивает сопоставление классов case (и потенциально подобных типов) с гетерогенными списками. Любой класс case имеет уникальное представление hlist, но любой заданный hlist соответствует очень большому числу классов потенциальных случаев. Например, если мы имеем следующие классы case:
case class Foo(i: Int, s: String)
case class Bar(x: Int, y: String)
Представление hlist, предоставляемое Generic
для Foo
и Bar
, равно Int :: String :: HNil
, что также является представлением для (Int, String)
и любых других классов случаев, которые мы могли бы определить с этими двумя типами в этом порядке.
(В качестве побочного примечания LabelledGeneric
позволяет различать Foo
и Bar
, так как он включает имена элементов в представлении как строки уровня типа.)
Обычно мы хотим указать класс case и позволить Shapeless определить (уникальное) общее представление, а создание Repr
члена типа (вместо параметра типа) позволяет нам сделать это довольно чисто. Если тип представления hlist был параметром типа, тогда ваши методы find
должны были бы иметь параметр типа Repr
, а это значит, что вы не сможете указать только T
и иметь Repr
> выведено.
Создание Repr
член типа имеет смысл только потому, что Repr
однозначно определяется параметром первого типа. Представьте себе тип типа типа Iso[A, B]
, который свидетельствует о том, что A
и B
являются изоморфными. Этот тип класса очень похож на Generic
, но A
не однозначно дермывает B
- мы не можем просто спросить "что такое тип, который изоморфен A
?" - так что это не будет полезно сделать B
членом типа (хотя мы могли бы, если бы мы действительно хотели - Iso[A]
просто ничего не значили бы).
Проблема с членами типа заключается в том, что их легко забыть, и как только они исчезнут, они ушли навсегда. Тот факт, что возвращаемый тип вашего find1
не уточнен (т.е. Не включает член типа), означает, что экземпляр Generic
, который он возвращает, в значительной степени бесполезен. Например, статический тип res0
здесь также может быть Any
:
scala> import shapeless._
import shapeless._
scala> def find1[T](implicit gen: Generic[T]): Generic[T] = gen
find1: [T](implicit gen: shapeless.Generic[T])shapeless.Generic[T]
scala> case class Foo(i: Int, s: String)
defined class Foo
scala> find1[Foo].to(Foo(1, "ABC"))
res0: shapeless.Generic[Foo]#Repr = 1 :: ABC :: HNil
scala> res0.head
<console>:15: error: value head is not a member of shapeless.Generic[Foo]#Repr
res0.head
^
Когда Shapeless Generic.materialize
macro создает экземпляр Generic[Foo]
, который мы запрашиваем, он статически вводится как Generic[Foo] { type Repr = Int :: String :: HNil }
, поэтому аргумент gen
, который компилятор передает find1
, имеет всю статическую информацию, которую мы необходимость. Проблема в том, что мы затем явно повышаем этот тип до простого старого unrefined Generic[Foo]
, и с этого момента компилятор не знает, что для этого экземпляра Repr
.
Scala зависимые от пути типы дают нам способ не забывать уточнение без добавления в наш метод другого параметра типа. В вашем find2
компилятор статически знает Repr
для входящего gen
, поэтому, когда вы говорите, что тип возврата Generic[T] { type Repr = gen.Repr }
, он сможет отслеживать эту информацию:
scala> find2[Foo].to(Foo(1, "ABC"))
res2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 1 :: ABC :: HNil
scala> res2.head
res3: Int = 1
Подводя итог: Generic
имеет параметр типа T
, который однозначно определяет член своего типа Repr
, Repr
является членом типа вместо параметра типа, поэтому нам не нужно включать его в все наши сигнатуры типа и зависимые от пути типы делают это возможным, позволяя нам отслеживать Repr
, даже если это не в наших сигнатурах типа.