涉及的问题如何Generic
and TypeClass
已实现,并且它们所做的事情足够不同,因此它们可能值得单独提出问题,所以我会坚持Generic
here.
Generic
提供从案例类(以及可能相似的类型)到异构列表的映射。任何案例类都有一个唯一的 hlist 表示,但任何给定的 hlist 都对应于非常非常大量的潜在案例类。例如,如果我们有以下案例类:
case class Foo(i: Int, s: String)
case class Bar(x: Int, y: String)
hlist 表示由Generic
对彼此而言Foo
and Bar
is Int :: String :: HNil
,这也是表示(Int, String)
以及我们可以按此顺序使用这两种类型定义的任何其他案例类。
(作为旁注,LabelledGeneric
让我们能够区分Foo
and Bar
,因为它在表示形式中包含成员名称作为类型级字符串。)
我们通常希望能够指定案例类并让 Shapeless 找出(唯一的)通用表示,并使得Repr
类型成员(而不是类型参数)允许我们非常干净地完成此操作。如果 hlist 表示类型是类型参数,那么您的find
方法必须有一个Repr
type 参数,这意味着您不能仅指定T
并拥有Repr
推断。
Making Repr
类型成员才有意义,因为Repr
由第一个类型参数唯一确定。想象一下像这样的类型类Iso[A, B]
见证那件事A
and B
是同构的。这个类型类非常类似于Generic
, but A
并不是唯一的真皮B
——我们不能只问“什么是the同构于的类型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
^
当无形的Generic.materialize
宏创建Generic[Foo]
我们要求的实例,它是静态类型的Generic[Foo] { type Repr = Int :: String :: HNil }
, 所以gen
编译器传递给的参数find1
拥有我们需要的所有静态信息。问题是我们然后显式将该类型向上转换为普通的旧的未精炼的类型Generic[Foo]
,从那时起编译器就不知道什么了Repr
就是针对这种情况。
Scala 的路径依赖类型为我们提供了一种不忘记细化的方法without向我们的方法添加另一个类型参数。在你的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
即使它不在我们的类型签名中。