从案例类中提取给定类型的值

时间:2014-11-15 22:50:09

标签: scala shapeless

使用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)

我是在正确的轨道上吗?

2 个答案:

答案 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,并LC通过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]
}