我正在为一些应用程序编写一个小注册模块,并使用类型类来执行此操作。主要逻辑基于喷涂路由:
val api = post {
path("register" / Rest) { rest =>
rest match {
case "user" => register[User]
// ... other cases
}
}
}
}
为了简化,我有以下用户数据类型:
case class User(id: String, email: String)
要从POST请求中提取实体,我正在使用类型类:
trait EntityExtractor[T] {
def extractFromRequest: Directive[T :: String :: HNil]
}
用户实施:
object User {
implicit val extractor = new EntityExtractor[User] {
def extractFromRequest: Directive[User :: String :: HNil] =
formFields('email, 'pass).hmap {
case email :: pass :: HNil =>
val id = UID.genId
User(id, email) :: pass :: HNil
}
}
}
我的register方法出现了问题,该方法使用了类型类指令:
def register[T] =
extractEntity[T].apply { entity => // fails here
validateEntity(entity) {
completeRegistration(entity)
}
}
}
def extractEntity[T: EntityExtractor]: Directive[Entity[T] :: HNil] = {
implicitly[EntityExtractor[T]].extractFromRequest.hmap {
case entity :: pass :: HNil => Entity(entity, pass) :: HNil
}
}
它失败并出现例外:could not find implicit value for evidence parameter of type EntityExtractor[T]
。
有没有办法用反射来解决这个问题(TypeTags或Manifest)没有模式匹配?
答案 0 :(得分:0)
希望以下有些人为的例子能够澄清整个问题。
考虑这段代码(与你当前的代码相对应):
object Main {
def main(args: Array[String]) {
implicit val intIntable = new Intable[Int] {
def toInt(obj: Int) = obj
}
println(squareAndDouble(10: Int))
}
// Corresponds to your register[T] function
def squareAndDouble[T](obj: T) = {
val d = double[T](obj)
d*d
}
// Corresponds to your extractEntity[T] function
def double[T: Intable](obj: T) = {
implicitly[Intable[T]].toInt(obj)*2
}
}
trait Intable[T] {
def toInt(obj: T): Int
}
此代码无法编译时出现以下错误:
test.scala:11: error: could not find implicit value for evidence parameter of type Intable[T]
val d = double[T](obj)
现在让我们使用隐式参数重写它(这是编译器在看到上下文绑定时为我们做的事情):
object Main {
def main(args: Array[String]) {
implicit val intIntable = new Intable[Int] {
def toInt(obj: Int) = obj
}
println(squareAndDouble(10: Int))
}
def squareAndDouble[T](obj: T) = {
val d = double[T](obj)
d*d
}
def double[T](obj: T)(implicit intable: Intable[T]) = {
intable.toInt(obj)*2
}
}
trait Intable[T] {
def toInt(obj: T): Int
}
此程序也不编译:
test.scala:11: error: could not find implicit value for parameter intable: Intable[T]
val d = double[T](obj)
现在应该更清楚为什么它不能编译。它失败只是因为T
函数内squareAndDouble
函数内的square
类型参数没有隐式值('证据')。double
函数需要它。由于squareAndDouble
函数需要其类型参数的证据(可以是任何内容),此证据在squareAndDouble
内的范围内出现的唯一方法是再次来自隐式参数,但现在在{{1}中功能。
基本上,这意味着您必须“传递”通用参数的证据才能使用类型类。
让我们解决它:
object Main {
def main(args: Array[String]) {
implicit val intIntable = new Intable[Int] {
def toInt(obj: Int) = obj
}
println(squareAndDouble(10: Int))
}
// Added context bound here
def squareAndDouble[T: Intable](obj: T) = {
val d = double[T](obj)
d*d
}
def double[T: Intable](obj: T) = {
implicitly[Intable[T]].toInt(obj)*2
}
}
trait Intable[T] {
def toInt(obj: T): Int
}
现在它编译并成功运行:
% scalac test.scala
% scala Main
400
另一种(可能更简单)理解问题的方法如下。我们的squareAndDouble[T]
函数接受无界类型参数T
,但随后它尝试调用另一个函数double[T: Intable]
,其类型参数有界。这是不允许的:如果它是合法的,那么就可以用任何类型调用squareAndDouble[T]
,即使没有Intable
类型类实例的那个,这也会因此而破坏{{1}功能。因此,我们必须将double
边界添加到Intable
函数以便编译:{{1}},然后它才能完美运行。
这实际上非常类似于使用类继承而不是隐式值的上/下边界。