首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在for-comprehension中使用if-guard来检查是否存在

在for-comprehension中使用if-guard来检查是否存在
EN

Stack Overflow用户
提问于 2018-09-18 19:30:16
回答 3查看 236关注 0票数 0

我正在进行3个数据库查询,每个查询都返回一个Future。我试图使用for理解来解析Future%s,但是我似乎没有在for中正确使用if

每个查询都依赖于前一个查询的结果。我查找一个token,如果找到了,我查找user,它找到了,我更新user。每个数据库查询都返回一个Future[Option]],我认为我可以根据前一个查询返回的是Some还是None来有条件地执行下一个查询。为此,我使用了isDefined。但是,当我为一个无效的令牌运行代码时,我得到了代码userOption:Option[User]<-userRepo.findUser(tokenOption.get.loginInfo); if tokenOption.isDefined的错误[NoSuchElementException: None.get]

代码语言:javascript
复制
def verifyUser(token:String) = Action.async {
  implicit request => {
    val result:Future[Result] = for{
      //generator 1 - get token from database
      tokenOption:Option[UserToken] <- userTokenRepo.find(UserTokenKey(UUID.fromString(token)))
      //generator2. found token, look for corresponding user to which the token belongs
      userOption:Option[User] <- userRepo.findUser(tokenOption.get.loginInfo); if tokenOption.isDefined
      //generator 3. found user and token. Update profile 
      modifiedUser:Option[User] <-  confirmSignupforUser(userOption.get); if userOption.isDefined
       } yield 
         { //check if we have user and token and modified user here. If any is missing, return error else success
           if(tokenOption.isDefined && userOption.isDefined && modifiedUser.isDefined)
              Redirect("http://localhost:9000/home"+";signup=success")//TODOM - pick from config
           else
             if(tokenOption.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else if(userOption.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else if(modifiedUser.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else //this shouldn't happen. Unexpected
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
         }
       result
     }
   }
EN

回答 3

Stack Overflow用户

发布于 2018-09-18 20:00:09

TL;DR

考虑使用OptionT https://typelevel.org/cats/datatypes/optiont.html

看一看我的柔和实现:

来自https://scastie.scala-lang.org/hsXXtRAFRrGpMO1Jl1Li7A

代码语言:javascript
复制
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await.result
import scala.concurrent.duration._
import scala.language.postfixOps

type UserToken = String
type User = String

def fromToken(token: String): Future[Option[UserToken]] = Future.successful(None)
def findUser(userToken: UserToken): Future[Option[User]] = Future.successful(None)
def modify(user: User): Future[Option[User]] = Future.successful(None)

def verifyUser(token: String) = {
  val result = for {
    tokenOption: Option[UserToken] <- fromToken(token) //generator 1 - get token from database
    userOption: Option[User] <- findUser(tokenOption.get);
    if tokenOption.isDefined //generator2. found token, look for corresponding user to which the token belongs
    modifiedUser: Option[User] <- modify(userOption.get);
    if userOption.isDefined //generator 3. found user and token. Update profile
  } yield { //check if we have user and token and modified user here. If any is missing, return error else success
    if (tokenOption.isDefined && userOption.isDefined && modifiedUser.isDefined)
      println("happy")
    else
      println("sad")
  }
  result
}

result(verifyUser("hello"), 1 second)

我使用了以下编译时间标志the last one is important

代码语言:javascript
复制
scalacOptions ++= Seq(
  "-deprecation",
  "-encoding", "UTF-8",
  "-feature",
  "-unchecked",
  "-Xprint:typer"
)

让我们关注编译输出的这一行:

代码语言:javascript
复制
(((tokenOption: Option[Playground.this.UserToken]) => Playground.this.findUser(tokenOption.get).
withFilter(((check$ifrefutable$2: Option[Playground.this.User]) => (check$ifrefutable$2: Option[Playground.this.User] @unchecked) match {
      case (userOption @ (_: Option[Playground.this.User])) => true
      case _ => false
...

您可以看到,在调用withFilter之前调用了tokenOption.get。这些get是您得到的异常的来源。

编译的几乎完整的输出是:

代码语言:javascript
复制
[[syntax trees at end of                     typer]] // main.scala
....
    import scala.concurrent.Future;
    import scala.concurrent.ExecutionContext.Implicits.global;
    import scala.concurrent.Await.result;
    import scala.concurrent.duration._;
    import scala.language.postfixOps;
    type UserToken = String;
    type User = String;
    def fromToken(token: String): scala.concurrent.Future[Option[Playground.this.UserToken]] = scala.concurrent.Future.successful[None.type](scala.None);
    def findUser(userToken: Playground.this.UserToken): scala.concurrent.Future[Option[Playground.this.User]] = scala.concurrent.Future.successful[None.type](scala.None);
    def modify(user: Playground.this.User): scala.concurrent.Future[Option[Playground.this.User]] = scala.concurrent.Future.successful[None.type](scala.None);
    def verifyUser(token: String): scala.concurrent.Future[Unit] = {
      val result: scala.concurrent.Future[Unit] = Playground.this.fromToken(token).withFilter(((check$ifrefutable$1: Option[Playground.this.UserToken]) => (check$ifrefutable$1: Option[Playground.this.UserToken] @unchecked) match {
  case (tokenOption @ (_: Option[Playground.this.UserToken])) => true
  case _ => false
}))(scala.concurrent.ExecutionContext.Implicits.global).flatMap[Unit](((tokenOption: Option[Playground.this.UserToken]) => Playground.this.findUser(tokenOption.get).withFilter(((check$ifrefutable$2: Option[Playground.this.User]) => (check$ifrefutable$2: Option[Playground.this.User] @unchecked) match {
  case (userOption @ (_: Option[Playground.this.User])) => true
  case _ => false
}))(scala.concurrent.ExecutionContext.Implicits.global).withFilter(((userOption: Option[Playground.this.User]) => tokenOption.isDefined))(scala.concurrent.ExecutionContext.Implicits.global).flatMap[Unit](((userOption: Option[Playground.this.User]) => Playground.this.modify(userOption.get).withFilter(((check$ifrefutable$3: Option[Playground.this.User]) => (check$ifrefutable$3: Option[Playground.this.User] @unchecked) match {
  case (modifiedUser @ (_: Option[Playground.this.User])) => true
  case _ => false
}))(scala.concurrent.ExecutionContext.Implicits.global).withFilter(((modifiedUser: Option[Playground.this.User]) => userOption.isDefined))(scala.concurrent.ExecutionContext.Implicits.global).map[Unit](((modifiedUser: Option[Playground.this.User]) => if (tokenOption.isDefined.&&(userOption.isDefined).&&(modifiedUser.isDefined))
        scala.Predef.println("happy")
      else
        scala.Predef.println("sad")))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global);
      result
    };
    scala.Predef.locally[Unit]({
      val $t: Unit = scala.concurrent.Await.result[Unit](Playground.this.verifyUser("hello"), scala.concurrent.duration.`package`.DurationInt(1).second);
      Playground.this.instrumentationMap$.update(com.olegych.scastie.api.Position.apply(1199, 1236), com.olegych.scastie.api.runtime.Runtime.render[Unit]($t)((ClassTag.Unit: scala.reflect.ClassTag[Unit])));
      $t
    })
  };
  object Main extends scala.AnyRef {
    def <init>(): Main.type = {
      Main.super.<init>();
      ()
    };
    private[this] val playground: Playground = new Playground();
    <stable> <accessor> def playground: Playground = Main.this.playground;
    def main(args: Array[String]): Unit = scala.Predef.println(com.olegych.scastie.api.runtime.Runtime.write(Main.this.playground.instrumentations$))
  }
}
票数 0
EN

Stack Overflow用户

发布于 2018-09-18 20:09:53

我不知道为什么您会惊讶地看到令牌无效的None.get出现错误:如果令牌无效,则tokenOptionNone,因此,下一条语句tokenOption.get将失败,并显示以下错误。

你想让“卫士”在你想要缩短的语句之前执行,而不是在它之后:

代码语言:javascript
复制
for {
   foo <- bar if foo.isDefined
   baz <- foo.get
 } yield baz

但这最终都会失败,因为没有什么可以使用yield (这个技巧适用于OptionsLists等,但如果不满足predicate,Future.withFilter将以失败告终,没有其他选择)。

避免此类错误的一般规则是永远不要在Option (或Try__)上使用.get。另外,永远不要在List上使用.head,在Map上使用.apply,等等。

这里有一种(几乎)惯用的方式来编写你想要的东西:

代码语言:javascript
复制
case object Error extends RuntimeException("")
userTokenRepo
  .find(UserTokenKey(UUID.fromString(token)))
  .map { _.getOrElse(throw Error)
  .flatMap { userRepo.find(_.loginInfo) }
  .map { _.getOrElse(throw Error) }
  .flatMap(confirmSignupForUser)
  .map { _.getOrElse(throw Error) }
  .map { _ => "success") }
  .recover { case Error => "error" }
  .map { result => Redirect(s"http://localhost:9000/home;signup=$result" }

注意,我说这“几乎”是惯用的,因为在scala中抛出异常是不受欢迎的。一个纯粹主义者会反对它,并建议使用像Try这样的东西。或者是偏向的Either,或者使用第三方库,如catsscalaz,这些库提供了额外的工具来使用OptionFuture(即OptionT)。

但我不建议现在就开始讨论这个问题。在开始使用高级scala之前,您应该对基本的“普通”scala有足够的了解,以避免出现一些完全无法理解的东西。

您也可以以完全惯用的方式(不使用异常)以不同的方式编写此代码,如下所示:

代码语言:javascript
复制
  userTokenRepo.find(UserTokenKey(UUID.fromString(token)))
    .flatMap { 
        case Some(token) => userRepo.find(token.loginInfo)
        case None => Future.successful(None) 
     }.flatMap { 
        case Some(user) => confirmSignupForUser(user)
        case None => Future.successful(None)
     }.map {
        case Some(_) => "success"
        case None => "error"
     }.map { result => 
        Redirect(s"http://localhost:9000/home;signup=$result"     
     }

这更“纯粹”,但更具重复性,所以我个人的偏好是第一个变体。

最后,您可以不再使用Error,只需直接处理NoSuchElement异常即可。这将是最短的,但即使对我来说也有点麻烦(如果一些下游代码因为bug而抛出这个异常呢?):

代码语言:javascript
复制
 userTokenRepo
   .find(UserTokenKey(UUID.fromString(token)))
   .flatMap { userRepo.find(_.get.loginInfo) }
   .flatMap(confirmSignupForUser(_.get))
   .map { _.get }
   .map { _ => "success") }
   .recover { case _: NoSuchElementException => "error" }
   .map { result => 
     Redirect(s"http://localhost:9000/home;signup=$result" 
   }

我真的不推荐最后一个版本,尽管它是最短的,而且可以说是最具可读性的版本(你甚至可以重写它,使它看起来更好)。使用Option.get通常被认为是“代码气味”,而且几乎从来都不是一件好事。

票数 0
EN

Stack Overflow用户

发布于 2018-09-18 22:24:33

How to best handle Future.filter predicate is not satisfied type errors激发

我像下面这样重写。当代码正常工作时,我很想知道我这样做的方式是否正确(函数式!)。它看起来很好吗?

代码语言:javascript
复制
   def verifyUser(token:String) = Action.async {
     implicit request => {
       println("verifyUser action called with token: " + token) //TODOM - add proper handling and response

       val result:Future[Result] = for{tokenOption:Option[UserToken] <- userTokenRepo.find(UserTokenKey(UUID.fromString(token)))  //generator 1 - get token from database
                                    userOption:Option[User] <- if (tokenOption.isDefined) userRepo.findUser(tokenOption.get.loginInfo) else Future.successful(None) //generator2. found token, look for corresponding user to which the token belongs
                                    modifiedUser:Option[User] <- if (userOption.isDefined) confirmSignupforUser(userOption.get) else Future.successful(None) //generator 3. found user and token. Update profile
                                    deletedToken:Option[UserTokenKey] <- if(modifiedUser.isDefined) userTokenRepo.remove(UserTokenKey(UUID.fromString(token))) else Future.successful(None)
       }
         yield { //check if we have user and token and modified user here. If any is missing, return error else success
           println("db query results tokenOption: "+tokenOption+", userOption: "+userOption+" : modifiedUserOption: "+modifiedUser+", deletedToken: "+deletedToken)
           if(tokenOption.isDefined && userOption.isDefined && modifiedUser.isDefined && deletedToken.isDefined)
              Redirect("http://localhost:9000/home"+";signup=success")//TODOM - pick from config
           else
             if(tokenOption.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else if(userOption.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else if(modifiedUser.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else //this shouldn't happen. Unexpected
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
         }
       result
     }
   }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52385618

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档