我正在编写一个使用安全社交和反应式图书馆的游戏2.3应用程序,使用scala。 现在我试图实现UserService [T]特性,但我在updatePasswordInfo方法上遇到编译错误。 这是方法:
def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
implicit val passwordInfoFormat = Json.format[PasswordInfo]
//the document query
val query = Json.obj("providerId" -> user.providerId,
"userId" -> user.userId
)
//search if the user exists
val futureUser: Future[Option[LoginUser]] = UserServiceLogin.find(query).one
futureUser map {
case Some(x) => val newPassword = Json.obj("passswordInfo" -> info)// the new password
UserServiceLogin.update(query, newPassword) //update the document
val newDocument: Future[Option[LoginUser]] = UserServiceLogin.find(query).one
newDocument map {
case Some(x) => x
case None => None
} //return the new LoginUser
case None => None
}
}
这是编译器错误:
/Users/alberto/git/recommendation-system/app/security/UserService.scala:203: type mismatch;
[error] found : scala.concurrent.Future[Product with Serializable]
[error] required: Option[securesocial.core.BasicProfile]
[error] newDocument map {
出了什么问题?
答案 0 :(得分:2)
如果您映射Future[A]
,那么您最终会得到Future[B]
,其中T
是您传递给map
的lambda返回的类型。
由于lambda在这种情况下返回Future[B]
,因此最终得到的Future[Future[B]]
与期望的类型不匹配。
简单的解决方法是使用flatMap
,它将lambda从A
转移到Future[B]
。
此外,您还要返回Option[LoginUser]
,但该方法应返回Option[BasicProfile]
。编译器推断出一个常见的超类型,在这种情况下是Product with Serializable
,因为它们都是案例类。
总结一下
scala.concurrent.Future[Product with Serializable]
^_____________________^^_________________________^
1 2
flatMap
代替map
BasicProfile
而不是LoginUser
,或将方法返回类型更改为Future[Option[LoginUser]]
顺便说一下,还有很大的改进空间,因为你可以使用for-comprehension和来自scalaz的OptionT
monad变换器来使整个事情变得更漂亮。
这是一个例子
import scalaz._; import Scalaz._
val newPassword = Json.obj("passswordInfo" -> info)
(for {
// this is done only for failing fast in case the user doesn't exist
_ <- optionT(UserServiceLogin.find(query).one)
_ <- optionT(Future.successful(Some(UserServiceLogin.update(query, newPassword))))
updatedUser <- optionT(UserServiceLogin.find(query).one)
} yield updatedUser).run
顺便说一句,这是假设update
是同步调用,这可能(并且我希望)不是这种情况。如果它返回Future[T]
,只需将代码更改为
_ <- optionT(UserServiceLogin.update(query, newPassword).map(Some(_)))
或者它已经返回Future[Option[T]]
_ <- optionT(UserServiceLogin.update(query, newPassword))
答案 1 :(得分:1)
有几种方法可以改进您的代码。
例如,您不需要在触发查询之前找到用户。
此外,最好检查您的查询是否实际成功(如果API允许)。
第三,我不确定LoginUser与BasicProfile的对应方式。您的代码似乎没有进行任何类型的转换。 如果 LoginUser是BasicProfile的子类,或者可以以某种方式强制转换为BasicProfile,您可以尝试这样的事情:
def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
implicit val passwordInfoFormat = Json.format[PasswordInfo]
//the document query
val query = Json.obj("providerId" -> user.providerId,
"userId" -> user.userId
)
UserServiceLogin.update(query, newPassword) //update the document
for {
user <- UserServiceLogin.find(query).one
} yield user.map(_.asInstanceOf[BasicProfile]) //return the new LoginUser
}
答案 2 :(得分:1)
如果你真的想让find快速失败(虽然它没那么有用),然后从数据库重新加载更新的用户,这样的事情应该不需要使用scalaz:
def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
implicit val passwordInfoFormat = Json.format[PasswordInfo]
//the document query
val query = Json.obj("providerId" -> user.providerId,
"userId" -> user.userId)
val newPassword = Json.obj("passswordInfo" -> info)
//update the document
for {
userO <- UserServiceLogin.find(query).one[BasicProfile] //fail fast (not sure this is really useful)
updatedUser<-UserServiceLogin.update(query, newPassword).map(_=>userO).recover{case _ =>None}
actualUser <- UserServiceLogin.find(query).one[BasicProfile]
} yield actualUser
}