从数据容器(例如案例类)中提取类型的最佳方法是什么。
例如,如果我有type Tagged[U] = { type Tag = U}
标记类型trait PID
,它是标记为Int type ProductId = Int with Tagged[PID]
或scalaz样式type ProductId = Int @@ PID
,并说明产品中的其他字段{{1}等等和包含产品属性的数据容器;
type Name = String @@ PName
如何在不诉诸反射的情况下编写通用提取器case class Product(pid: ProductId, name: Name, weight: Weight)
样式方法?
原因是我想在运行时从Product容器中动态提取字段。即用户传递他们想要提取的产品的属性。
即。如果我想动态获取A => B
,我可以编写一个接受类型并返回值的方法,例如
ProductId
或者我过度复杂化了。
我可以写一个简单的提取器类,它取A => B功能并为每种类型定义;
trait Extractor[A] {
def extract[B](i: A): B = //get type B from the Product class
}
然后在需要的地方添加它们。
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
}
但这似乎很麻烦。
我觉得Lens在这里很有用。
答案 0 :(得分:8)
为了完整的例子,假设我们有以下类型和一些示例数据:
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
的工作方式相同。现在假设我们希望能够在任意案例类中按类型查找成员,我们可以使用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))
}
请注意,我为了简单和简洁而使用结构类型,但您可以非常轻松地定义一个可以执行相同操作的新类。
现在我们可以写下以下内容:
scala> extract[ProductId].from(product)
res0: Int with shapeless.tag.Tagged[PID] = 13
请注意,如果案例类具有多个具有所请求类型的成员,则将返回第一个成员。如果它没有任何具有正确类型的成员(例如extract[Char] from(product)
之类的东西),那么你将得到一个很好的编译时错误。
你可以在这里使用镜头,但你需要写更多或更少相同的机器 - 我不知道镜头实现,它给你按类型索引(例如,Shapeless提供位置索引和成员名称索引)。
(请注意,这不是真正的“动态”,因为您在案例类中查找的类型必须在编译时静态知道,但在上面给出的示例中也是这种情况。)< / p>