在理解中使用if-guard来检查是否存在

时间:2018-09-18 11:30:16

标签: scala

我正在进行3个数据库查询,每个查询返回一个Future。我正在尝试使用for理解来解决Future,但是似乎我在if

中没有正确使用for

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

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
     }
   }

3 个答案:

答案 0 :(得分:0)

TL; DR

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


看看我的精简版实现:

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

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

scalacOptions ++= Seq(
  "-deprecation",
  "-encoding", "UTF-8",
  "-feature",
  "-unchecked",
  "-Xprint:typer"
)

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

(((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
...

您会看到tokenOption.getwithFilter之前被调用。这些get是您所获得的异常的来源

几乎完整的编译输出为:

[[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$))
  }
}

答案 1 :(得分:0)

我不确定您为什么惊讶并发现None.get带有无效令牌的错误:如果令牌无效,则tokenOptionNone,因此下一条语句{{ 1}}将完全由于此错误而失败。

您要在要短路的语句之前而不是在语句后执行“后卫”:

tokenOption.get

但这最终还是会失败,因为for { foo <- bar if foo.isDefined baz <- foo.get } yield baz 毫无用处(此技巧适用于yieldOptions等,但是Lists将结束如果谓词不满足,则会失败,没有其他选择)。

避免此类错误的一般规则是切勿在{{1​​}}(或Future.withFilter)上使用.get 。另外,切勿在{{1​​}}上使用Option,在Try上使用.head,等等。

这是一种(几乎)惯用的方式来编写您想要的内容:

List

注意,我说这是“几乎”惯用的,因为在scala中抛出异常是不受欢迎的。纯粹主义者会反对它,并建议使用.apply之类的东西。或带有偏见的Map,或者利用第三方库,例如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" } Try,它们提供了用于处理{{1 }}(即Either)。

但是我不建议现在就开始讨论。在开始使用高级的东西之前,您应该对基本的“香草” scala有足够的了解,以免最终导致完全无法理解的东西。

您还可以以完全惯用的方式(不使用异常)编写不同的内容,例如:

cats

这是“纯”的,但重复性更高,所以我的个人偏爱是第一种。

最后,您可以消除我的scalaz麻烦,而直接处理Future异常。这将是最短的,但即使按我的口味,还是有点讨厌(如果某些下游代码由于错误而抛出此异常怎么办?):

Option

真的虽然不建议使用最新版本,尽管它是最短的版本,并且可以说是可读性最高的版本(您甚至可以用理解的方式重写它,以使其看起来更好)。 。使用OptionT通常被认为是“代码异味”,几乎绝不是一件好事。

答案 2 :(得分:0)

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

我重写如下。当代码工作时,我很好奇我是否以正确的方式(功能!)进行操作。看起来还好吗?

   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
     }
   }