使用Shapeless,是否有可能从案例类中提取特定类型的值?到目前为止,我可以这样做:
def fromCaseClass[T, R <: HList](value: T)(implicit ga: Generic.Aux[T, R]): R = {
ga.to(value)
}
然后允许我在程序上提取值:
scala> case class ServiceConfig(host: String, port: Int, secure: Boolean)
defined class ServiceConfig
scala> val instance = ServiceConfig("host", 80, true)
instance: ServiceConfig = ServiceConfig(host,80,true)
scala> fromCaseClass(instance).select[Boolean]
res10: Boolean = true
scala> fromCaseClass(instance).select[Int]
res11: Int = 80
然而,当我尝试编写一个函数来执行此操作时,我会被未找到的隐含绑定:
def getByType[C, X](value: C)(implicit ga: Generic.Aux[C, X]): X = {
fromCaseClass(value).select[X]
}
<console>:12: error: could not find implicit value for parameter ga: shapeless.Generic.Aux[C,R]
fromCaseClass(value).select[X]
据推测,我得到了这个,因为编译器无法验证我的参数不是案例类。我有办法做到这一点吗?
我对Shapeless很陌生,所以我不能完全确定我是不是想要做一些疯狂的事情,或者是因为缺少一些简单的东西。
我觉得我离得更近了。我可以这样实现:
def newGetByType[C, H <: HList, R]
(value: C)
(implicit ga: Generic.Aux[C, H], selector: Selector[H, R]): R = {
ga.to(value).select[R]
}
这允许我从案例类中选择:
scala> val s: String = newGetByType(instance)
s: String = host
但这似乎只适用于案例类中的第一种类型:
scala> val i: Int = newGetByType(instance)
<console>:17: error: type mismatch;
found : String
required: Int
val i: Int = newGetByType(instance)
我是在正确的轨道上吗?
答案 0 :(得分:7)
你越来越近了......
newGetByType
的主要问题在于它没有任何方法可以明确指定您希望提取的类型(并且,正如您所观察到的那样,输入LHS上的val不足以推断它。
为什么不能明确指定要提取的类型?这里再次定义,
def getByType[S, C, L <: HList](value: C)
(implicit gen: Generic.Aux[C, L], sel: Selector[L, S]): S =
gen.to(value).select[S]
理想情况下,我们希望能够指定类型参数S
,允许从C
参数推断value
,并L
为C
通过class GetByType[S] {
def apply[C, L <: HList](value: C)
(implicit gen: Generic.Aux[C, L], sel: Selector[L, S]): S =
gen.to(value).select[S]
}
的隐式分辨率计算得出。不幸的是,Scala不允许我们部分指定类型参数......它全部或全部。
因此,要使其工作的技巧是将类型参数列表拆分为两个:一个可以完全明确指定,一个可以完全推断:这是一种通用技术,而不是一个特定于无形的技术。
我们通过将计算的主要部分移动到辅助类来完成此操作,该辅助类由我们将明确提供的类型进行参数化,
S
请注意,我们现在可以假设已知apply
,并且可以推断出getByType
的两个类型参数。我们完成了def getByType[S] = new GetByType[S]
的现在微不足道的定义,它只提供了显式类型参数可以去的地方,并实例化了辅助类,
scala> import shapeless._, ops.hlist._
import ops.hlist._
scala> :paste
// Entering paste mode (ctrl-D to finish)
class GetByType[S] {
def apply[C, L <: HList](value: C)
(implicit gen: Generic.Aux[C, L], sel: Selector[L, S]): S =
gen.to(value).select[S]
}
// Exiting paste mode, now interpreting.
defined class GetByType
scala> def getByType[S] = new GetByType[S]
getByType: [S]=> GetByType[S]
scala> case class ServiceConfig(host: String, port: Int, secure: Boolean)
defined class ServiceConfig
scala> val instance = ServiceConfig("host", 80, true)
instance: ServiceConfig = ServiceConfig(host,80,true)
scala> getByType[String](instance)
res0: String = host
scala> getByType[Int](instance)
res1: Int = 80
scala> getByType[Boolean](instance)
res2: Boolean = true
这可以为您提供所需的结果,
{{1}}
答案 1 :(得分:0)
我猜你必须明确地通过ga
调用,否则它将不在被调用函数的范围内(即fromCaseClass
):
def getByType[C, X](value: C)(implicit ga: Generic.Aux[C, X]): X = {
fromCaseClass(value)(ga).select[X]
}
其他方法是在调用fromCaseClass
之前将此参数放入范围:
def getByType[C, X](value: C)(implicit ga: Generic.Aux[C, X]): X = {
val innerGa = ga
fromCaseClass(value).select[X]
}