Scala类型类模式和通用方法

4

我想编写一个通用的提取器,使用spray和spray-json解析json POST请求体。

但是,我在尝试将其与多个模型一起使用时遇到了困难。以下是服务对象中的case语句:

import MyJsonProtocol._

...

def receive = {
  case Post (Routes.person.post, p: Person) => sender ! Ok(Actions.person.post(p))
  case Get  (Routes.foo.forId(x))           => sender ! Ok(x)
  case _                                    => sender ! Ok("No handler")
}

以下是我编写的提取器(只要在case语句的作用域内存在单个模型的JsonReader即可工作):

//NB. Json.parse returns an Option[T]
object Post extends Request {
  def unapply[T:JsonReader](req: HttpRequest): Option[(String, T)] = req match {
    case HttpRequest(POST, url, _, HttpBody(_, body), _) => Json.parse[T](body.asString).map((url, _))
    case _ => None
  }
}

然而,一旦我添加了新的模型(及其相关的JsonReader),这段代码就无法编译,并显示如下错误:

ambiguous implicit values:
[error]  both value personFormat in object Json of type => spray.json.RootJsonFormat[com.rsslldnphy.foam.models.Person]
[error]  and value animalFormat in object Json of type => spray.json.RootJsonFormat[com.rsslldnphy.foam.models.Animal]
[error]  match expected type spray.json.JsonReader[T]
[error]     case Post (Routes.person.post, p: Person) => sender ! Ok(Actions.person.post(p))
JsonReaders的通用类型不同的事实似乎被忽略了。这是类型擦除吗?有没有办法绕过它来获得我想要的结果? 这是目前项目的完整可编译代码,其中ExampleService中的注释解释了它的问题所在:github.com/rsslldnphy/foam。非常感谢您的帮助。如果目前无法实现我的需求,是否有其他替代方法可以建议?

你尝试过 case Post[Person] 吗? - Kim Stebel
我得到了一个错误信息:“未找到类型为Post”。我没有定义一个类,只是一个对象。 - Russell
也许如果您希望在答案中获得可编译的代码,您应该在问题中提供相同的内容。 - Kim Stebel
我并不是要求可编译的代码,只是想要一个解释你的意思以及为什么它应该有效的说明!但如果有帮助的话,我可以将整个项目上传到Github。 - Russell
@KimStebel,我已经在问题中添加了一个Github链接。感谢您抽出时间来帮忙。 - Russell
1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
2
你需要明确告诉编译器如何使其工作。正如下面所示,编译器无法推断T应该是什么。编译器需要能够动态地查看请求中的Json,并从中推断出一种类型(我们只能梦想它能做到;)
def unapply[T:JsonReader](req: HttpRequest): Option[(String, T)] = (...) Json.parse[T] (...)

这意味着要使此功能正常工作,您必须明确注释如下所示的帖子:

import MyJsonProtocol._

...

def receive = {
  case Post[Person] (Routes.person.post, p: Person) => sender ! Ok(Actions.person.post(p))
  case Get  (Routes.foo.forId(x))           => sender ! Ok(x)
  case _                                    => sender ! Ok("No handler")
}

并将定义更改为:

case class Post[T: JsonReader] extends Request {
  def unapply(req: HttpRequest): Option[(String, T)] = req match {
    case HttpRequest(POST, url, _, HttpBody(_, body), _) => Json.parse[T](body.asString).map((url, _))
    case _ => None
  }
}

[error] /Users/russell.dunphy/Code/Personal/foam/src/main/scala/com/rsslldnphy/foam/ExampleService.scala:31: not found: type Post [error] case Post[Person] (Routes.person.post, p: Person) => sender ! Ok(Actions.person.post(p)) - Russell
编译器难道不能从我明确命名为 p: Person 的事实中推断出 T 的类型吗?或者从它被传递到的方法 Actions.person.post 明确要求一个 Person 作为其参数的事实中推断出吗? - Russell
不,Scala 推断能力并不那么强大,它无法从你返回的内容中推断出类型。另外,unapply 是一个奇怪的情况——我正在思考如何使 unapply 参数化。 - Reuben Doetsch
谢谢,但它仍然无法编译:[error] /Users/russell.dunphy/Code/Personal/foam/src/main/scala/com/rsslldnphy/foam/ExampleService.scala:31: 类型为com.rsslldnphy.foam.routing.Post的类不接受类型参数。 [error] case Post[Person] (Routes.person.post, p: Person) => sender ! Ok(Actions.person.post(p)) - Russell
不好意思,我意识到这个问题的难度,正在寻找我在这个主题上发现的博客文章。今晚我会再仔细看看。 - Reuben Doetsch
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,