我试图了解@foreach (var item in model.heatingArea)
{
//what ever you need to do here
}
的工作方式(以及Generic
)。 github wiki在示例和文档上非常稀少。是否有详细描述TypeClass
和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
答案 0 :(得分:51)
Generic
和TypeClass
的实施方式以及他们所做的工作所涉及的问题不同,他们可能应该分别提出问题,所以我会坚持Generic
。
Generic
提供了从案例类(和可能类似的类型)到异构列表的映射。任何案例类都有唯一的hlist表示,但任何给定的hlist都对应于非常非常大量的潜在案例类。例如,如果我们有以下案例类:
case class Foo(i: Int, s: String)
case class Bar(x: Int, y: String)
Generic
为Foo
和Bar
提供的hlist表示形式为Int :: String :: HNil
,这也是(Int, String)
和任何其他案例类的代表可以按此顺序定义这两种类型。
(作为旁注,LabelledGeneric
允许我们区分Foo
和Bar
,因为它将表示中的成员名称包含为类型级别的字符串。)
我们通常希望能够指定案例类,让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
宏创建我们要求的Generic[Foo]
实例时,它会被静态输入为Generic[Foo] { type Repr = Int :: String :: HNil }
,因此编译器提交的gen
参数{ {1}}包含我们需要的所有静态信息。问题是我们然后明确地将该类型向上转换为普通的未定义find1
,并且从那时起,编译器不知道该实例的Generic[Foo]
是什么。
Scala的路径依赖类型为我们提供了一种不遗漏优化而不向我们的方法添加另一个类型参数的方法。在Repr
中,编译器静态地知道传入find2
的{{1}},因此当您说返回类型为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
,即使它不在我们的类型签名中。