Подтвердить что ты не робот

Безшовные: Generic.Aux

Я пытаюсь понять, как работает GenericTypeClass). Вики файлы github очень скудны по примерам и документации. Есть ли каноническая страница блога/документации, подробно описывающая Generic и TypeClass?

В конкретном, в чем разница между этими двумя способами?:

def find1[T](implicit gen: Generic[T]): Generic[T] = gen
def find2[T](implicit gen: Generic[T]): Generic[T] { type Repr = gen.Repr } = gen

дано

object Generic {
  type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }
  def apply[T](implicit gen: Generic[T]): Aux[T, gen.Repr] = gen
  implicit def materialize[T, R]: Aux[T, R] = macro GenericMacros.materialize[T, R]
}
4b9b3361

Ответ 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, даже если это не в наших сигнатурах типа.