无形:Generic.Aux [英] Shapeless: Generic.Aux

查看:87
本文介绍了无形:Generic.Aux的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图理解 Generic 是如何工作的(以及 TypeClass )。 github wiki在示例和文档上非常稀少。有详细描述 Generic TypeClass 的规范博客帖子/文档页面吗?



具体来说,这两种方法有什么区别?:

  def find1 [T](隐式gen:Generic [T]):Generic [T] = gen 
def find2 [T](隐式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]):辅助[T,R] =宏GenericMacros.materialize [T,R]
}
隐含def实现[T,R]:Aux [T,gen.Repr] = gen
code>


解决方案

涉及 Generic TypeClass 被实现,它们做的不同,它们可能有不同的问题,所以我会坚持 Generic here。



Generic 提供了从案例类(以及可能类似的类型)到异构列表的映射。任何案例类都有一个独特的hlist表示,但是任何给定的hlist对应于非常非常多的潜在案例类。例如,如果我们有以下案例类:

pre code case case class Foo(i:Int,s:String)
case class Bar(x:Int,y:String)

由<$ c对于 Foo Bar ,$ c> Generic 是 Int :: String :: HNil ,它也是(Int,String)和其他任何可以用这两种类型定义的case类的表示(作为附注, LabelledGeneric 允许我们区分 Foo Bar ,因为它将表示中的成员名称作为类型级别的字符串。)



我们通常希望能够指定case类,并让Shapeless找出(唯一)通用表示,并使 Repr 成为一个类型成员(而不是一个类型参数)可以让我们干净地做到这一点。如果hlist表示类型是一个类型参数,那么您的 find 方法也必须具有 Repr 类型参数,这意味着你不能只指定 T 并推断 Repr



制作 Repr 类型成员仅仅因为 Repr 是有意义的由第一个类型参数唯一确定。设想一个类型类,如 Iso Iso [A,B] ,它证明 A B 是同构的。这种类型与 Generic 非常相似,但 A 并不是唯一地触发 B - 我们不能问什么是与 A ?同构的类型 - 所以它不会将 B 作为一个类型成员是有用的(尽管我们可以如果我们真的想要 - Iso [A] just wouldn 'b
$ b

类型成员的问题在于它们很容易被遗忘,一旦它们消失了,它们就会永远消失。您的 find1 的返回类型没有完善(即不包含类型成员)的事实意味着 Generic 实例几乎没用。例如,这里的 res0 的静态类型可能也是 Any

  scala> import shapeless._ 
import shapeless._

scala> def find1 [T](隐式gen:Generic [T]):Generic [T] = gen
find1:[T](隐式gen:shapeless.Generic [T])shapeless.Generic [T]

scala> case class Foo(i:Int,s:String)
定义的类Foo

scala> find1 [Foo] .to(Foo(1,ABC))
res0:shapeless.Generic [Foo] #Repr = 1 :: ABC :: HNil

scala> res0.head
< console>:15:error:值头不是shapeless.Generic [Foo]的成员#Repr
res0.head
^

当Shapeless的 Generic.materialize 宏创建 Generic [Foo] 我们要求的实例,静态类型为 Generic [Foo] {type Repr = Int :: String :: HNil} ,因此编译器提供给 find1 gen 参数具有我们需要的所有静态信息。问题是,我们然后明确地将该类型转换为一个普通的未定义的 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>总结: > Generic 有一个类型参数 T ,它唯一确定了它的类型成员 Repr Repr 是一个类型成员,而不是一个类型参数,因此我们不必将其包含在所有类型签名中,而路径依赖类型使这成为可能,从而使我们追踪 Repr ,即使它不在我们的类型签名中。


I'm trying to understand how Generic works (and TypeClass too). The github wiki is very sparse on examples and documentation. Is there a canonical blog post / documentation page describing Generic and TypeClass in detail?

In concrete, what is the difference between these two methods?:

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

given

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]
}

解决方案

The issues involved in how Generic and TypeClass are implemented and what they do are different enough that they probably deserve separate questions, so I'll stick to Generic here.

Generic provides a mapping from case classes (and potentially similar types) to heterogeneous lists. Any case class has a unique hlist representation, but any given hlist corresponds to a very, very large number of potential case classes. For example, if we have the following case classes:

case class Foo(i: Int, s: String)
case class Bar(x: Int, y: String)

The hlist representation provided by Generic for both Foo and Bar is Int :: String :: HNil, which is also the representation for (Int, String) and any other case classes we could define with these two types in this order.

(As a side note, LabelledGeneric allows us to distinguish between Foo and Bar, since it includes the member names in the representation as type-level strings.)

We generally want to be able to specify the case class and let Shapeless figure out the (unique) generic representation, and making Repr a type member (instead of a type parameter) allows us to do this pretty cleanly. If the hlist representation type were a type parameter, then your find methods would have to have a Repr type parameter as well, which means that you wouldn't be able to specify only the T and have the Repr inferred.

Making Repr a type member makes sense only because the Repr is uniquely determined by the first type parameter. Imagine a type class like Iso[A, B] that witnesses that A and B are isomorphic. This type class is very similar to Generic, but A doesn't uniquely dermine B—we can't just ask "what is the type that's isomorphic to A?"—so it wouldn't be useful to make B a type member (although we could if we really wanted to—Iso[A] just wouldn't really mean anything).

The problem with type members is that they're easy to forget, and once they're gone, they're gone forever. The fact that the return type of your find1 isn't refined (i.e. doesn't include the type member) means that the Generic instance it returns is pretty much useless. For example, the static type of res0 here might as well be 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
                   ^

When Shapeless's Generic.materialize macro creates the Generic[Foo] instance we're asking for, it's statically typed as a Generic[Foo] { type Repr = Int :: String :: HNil }, so the gen argument that the compiler hands to find1 has all the static information we need. The problem is that we then explicitly up-cast that type to a plain old unrefined Generic[Foo], and from that point on the compiler doesn't know what the Repr is for that instance.

Scala's path-dependent types give us a way not to forget the refinement without adding another type parameter to our method. In your find2, the compiler statically knows the Repr for the incoming gen, so when you say that the return type is Generic[T] { type Repr = gen.Repr }, it will be able to keep track of that information:

scala> find2[Foo].to(Foo(1, "ABC"))
res2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 1 :: ABC :: HNil

scala> res2.head
res3: Int = 1

To sum up: Generic has a type parameter T that uniquely determines its type member Repr, Repr is a type member instead of a type parameter so that we don't have to include it in all of our type signatures, and path-dependent types make this possible, allowing us to keep track of Repr even though it's not in our type signatures.

这篇关于无形:Generic.Aux的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆