基于Scala类型的属性提取器-仅Getter镜头?

基于Scala类型的属性提取器-仅Getter镜头?,scala,generics,scalaz,shapeless,lenses,Scala,Generics,Scalaz,Shapeless,Lenses,从数据容器(如case类)提取类型的最佳方法是什么 例如,如果我有一个type taged[U]={type Tag=U}taged typetrait PID,它是一个带taged[PID]的taged Inttype ProductId=Int或scalaz styletype ProductId=Int@@PID并在producttype Name=String@@PName等和包含产品属性的数据容器中说出其他字段 case class Product(pid: ProductId, na

从数据容器(如case类)提取类型的最佳方法是什么

例如,如果我有一个
type taged[U]={type Tag=U}
taged type
trait PID
,它是一个带taged[PID]的taged Int
type ProductId=Int
或scalaz style
type ProductId=Int@@PID
并在product
type Name=String@@PName
等和包含产品属性的数据容器中说出其他字段

case class Product(pid: ProductId, name: Name, weight: Weight)
我如何编写一个通用的提取器
a=>B
风格的方法而不诉诸反射

原因是我想在运行时从产品容器中动态提取一个字段。即,用户传入他们想要提取的产品的属性

也就是说,如果我想动态获取
ProductId
,我可以写一个方法来获取类型并返回一个值吗

trait Extractor[A] {
  def extract[B](i: A): B = //get type B from the Product class
}
还是我把事情复杂化了

我可以编写一个简单的提取器类,它接受一个=>B函数,并为每种类型定义它

trait Getter[A, B] {
  def extract(i: A): B
}
//... mix this in...
trait GetPID extends Getter[Product, ProductId] {
  override def extract(implicit i: Product) = i.pid
}
trait GetName extends Getter[Product, Name] {
  override def extract(implicit i: Product) = i.name
}
然后在需要的地方添加它们

val dyn = new DynamicProductExtractor with GetPID 
dyn.extract
但这似乎是一个麻烦


我相信类似镜头的东西在这里会很有用。

为了完整的示例,假设我们有以下类型和一些示例数据:

import shapeless._, tag._

trait PID; trait PName; trait PWeight

type ProductId = Int @@ PID
type Name = String @@ PName
type Weight = Double @@ PWeight

case class Product(pid: ProductId, name: Name, weight: Weight)

val pid = tag[PID](13)
val name = tag[PName]("foo")
val weight = tag[PWeight](100.0)

val product = Product(pid, name, weight)
我在这里使用的是Shapeless的标记,但下面的所有内容都与Scalaz或您自己的
标记的
相同。现在假设我们希望能够在任意case类中按类型查找成员,我们可以使用Shapeless的
Generic
创建提取器:

import ops.hlist.Selector

def extract[A] = new {
  def from[C, Repr <: HList](c: C)(implicit
    gen: Generic.Aux[C, Repr],
    sel: Selector[Repr, A]
  ) = sel(gen.to(c))
}
请注意,如果case类具有多个请求类型的成员,则将返回第一个成员。如果它没有任何类型正确的成员(例如,
extract[Char]from(product)
),您将得到一个很好的编译时错误

您可以在这里使用镜头,但您需要编写大致相同的机制—我不知道有哪种镜头实现可以按类型进行索引(例如,Shapeless提供位置索引和按成员名称进行索引)


(请注意,这并不是真正的“动态”,因为您在case类中查找的类型在编译时必须是静态已知的,但在上面给出的示例中也是如此。)

感谢您的精彩回答!这看起来很整洁!性能方面,这是怎么回事?这与反射相比如何?@NightWolf在运行时不会发生任何解析,因此您实际上只需将案例类实例转换为
HList
并应用选择器即可。在大多数情况下,这些操作应该可以忽略不计(如果不是比反射更好的话,那么肯定接近反射,并且注意这种方法有额外的安全好处),但是您当然应该自己进行评估<代码>scala>案例类产品(pid:ProductId,名称:name,权重:weight)错误:类型不匹配;found:Double required:AnyRef注意:scala.Double=>java.lang.Double中存在一个隐式函数,但从对象继承的方法呈现不明确。这是为了避免将任何scala.Double转换为任何AnyRef的包层隐式。您可能希望使用类型归属:
x:java.lang.Double
@TravisBrown,有什么想法吗?有没有办法让这个动态/泛型,这样我就可以编写一个特性来实现泛型的摘录?@NightWolf您能发布完整的代码和版本吗?上面的代码在Scala 2.11.5和Shapeless 2.1.0上适用,我一眼就看不出你的代码有什么问题。
scala> extract[ProductId].from(product)
res0: Int with shapeless.tag.Tagged[PID] = 13