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

Scala Type Based Attribute Extractor - Getter only Lens?

从数据容器(如案例)中提取类型的最佳方法是什么 class。

例如,如果我有一个 type Tagged[U] = { type Tag = U} 标记类型 trait PID,它是一个标记 Int type ProductId = Int with Tagged[PID] 或 scalaz 样式 type ProductId = Int @@ PID 并说产品中的其他字段 type Name = String @@ PNameetc 和一个包含产品属性的数据容器;

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

如何在不借助反射的情况下编写通用提取器 A => B 样式的方法?

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

即如果我想动态获取 ProductId ,我可以编写一个采用类型和 returns 值的方法,例如

trait Extractor[A] {
  def extract[B](i: A): B = //get type B from the Product class
}

还是我把事情复杂化了。

我可以编写简单的提取器 class,它采用 A => 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

但这似乎很麻烦。

我想 Lens 之类的东西在这里很有用。

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

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 的或您自己的 Tagged 一起使用。现在假设我们希望能够在任意情况下按类型查找成员 class,我们可以使用 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))
}

请注意,为了简洁起见,我使用结构类型,但您可以很容易地定义一个新的 class 来做同样的事情。

现在我们可以这样写了:

scala> extract[ProductId].from(product)
res0: Int with shapeless.tag.Tagged[PID] = 13

请注意,如果案例 class 有多个具有请求类型的成员,将返回第一个。如果它没有任何类型正确的成员(例如 extract[Char] from(product)),你会得到一个很好的编译时错误。

你可以在这里使用镜头,但你需要或多或少地编写同样的机器——我不知道镜头实现可以让你按类型索引(例如 Shapeless 提供位置索引和索引会员名).

(请注意,这并不是真正的 "dynamic",因为您在 class 案例中查找的类型必须在编译时静态已知,但在你上面给出的例子。)