【问题标题】:Scala: better nested multiple condition checkScala:更好的嵌套多条件检查
【发布时间】:2014-09-08 04:30:13
【问题描述】:

最近,我经常写出这样的代码:

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) {
  arg1.get(arg2) match {
    case Some(value1) =>
      arg3.get(value1) match {
        case Some(value2) =>
          arg4.get(arg5, value2) match {
            case Some(value3) =>
              finallyDoSomethingInside(value3)
            case None =>
              log("Some excuse for being unable to work with arg4/arg5...")
          }
        case None =>
          log("Some excuse for being unable to work with arg3")
      }
    case None =>
      log("Some excuse for being unable to work with arg1/arg2")
  }
}

somewhat related question 似乎大力提倡使用嵌套的match,尽管从我的角度来看,它似乎难以阅读、简洁或易于理解:(1) 它有点拆分支票本身及其后果,(2)它使代码无法控制地嵌套,没有任何真正的嵌套理由。在这些特殊情况下,我很乐意将代码构造成以下几行:

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) {
  // Step 1
  val value1Opt = arg1.get(arg2)
  if (value1Opt.isEmpty) {
    log("Some excuse for being unable to work with arg1/arg2")
    return
  }
  val value1 = value1Opt.get

  // Step 2
  val value2Opt = arg3.get(value1)
  if (value2Opt.isEmpty) {
    log("Some excuse for being unable to work with arg3")
    return
  }
  val value2 = value2Opt.get

  // Step 3
  val value3Opt = arg4.get(arg5, value2)
  if (value3Opt.isEmpty) {
    log("Some excuse for being unable to work with arg4/arg5...")
    return
  }
  val value3 = value3Opt.get

  // All checked - we're free to act!
  finallyDoSomethingInside(value3)
}

然而,这种模式(即valueXOpt = (...).get => 检查isEmpty => value = valueXOpt.get)看起来真的很难看,而且也绝对过于冗长。见鬼,Java 版本看起来更简洁:

Value1Type value1 = arg1.get(arg2);
if (value1 != null) {
    log("Some excuse for being unable to work with arg1/arg2");
    return;
}

是否有更好、更简洁的替代方案,即获取值并指定替代的短逃逸路线(log 一行 + return),而不需要与匹配项嵌套?

【问题讨论】:

    标签: scala nested pattern-matching


    【解决方案1】:

    这个怎么样?

    object Options{
      implicit class OptionLog[T](val option:Option[T]) extends AnyVal{
    
        def ifNone(body: =>Unit):Option[T] = option.orElse {
           body
           option
        }
       }
    }
    
    import Options._
    
    def something(arg1:Option[Int], arg2:Option[String], arg3:Option[Long], arg4:Option[Any]){
      for{
        val1 <- arg1 ifNone(println("arg1 was none"))
        val2 <- arg2 ifNone(println("arg2 was none"))
        val3 <- arg3 ifNone(println("arg3 was none"))
      }{
        println(s"doing something with $val1, $val2, $val3")
      }
    }
    

    然后……

    scala> something(Some(3), Some("hello"), None, Some("blah"))
    arg3 was none
    
    scala> something(Some(3), Some("hello"), Some(10l), Some("blah"))
    doing something with 3, hello, 10
    

    【讨论】:

    【解决方案2】:

    也许你的意思是,对于一个条件x

    scala> def f(x: Option[Int]): Int = x orElse { println("nope"); return -1 } map (_ + 1) getOrElse -2
    f: (x: Option[Int])Int
    
    scala> f(Some(5))
    res3: Int = 6
    
    scala> f(None)
    nope
    res4: Int = -1
    

    甚至

    scala> def f(x: Option[Int], y: Option[Int]): Int = (for (i <- x orElse { println("nope"); return -1 }; j <- y orElse { println("gah!"); return -2 }) yield i + j) getOrElse -3
    f: (x: Option[Int], y: Option[Int])Int
    
    scala> f(Some(5), None)
    gah!
    res5: Int = -2
    

    对不起,如果我过于简单化了。

    【讨论】:

    • 我认为你是对的,但我懒得看,因为你的代码都被压缩成单行,请美化一下
    • @samthebest 我也懒得理会。哦,好吧。
    【解决方案3】:

    你不想使用 map 方法吗?

    def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) =
      arg1.get(arg2).map(value1 =>
        arg3.get(value1).map(value2 =>
          arg4.get(arg5, value2).map(value3 => 
            finallyDoSomethingInside(value3)).
          getOrElse(log("Some excuse for being unable to work with arg4/arg5"))).
        getOrElse(log("Some excuse for being unable to work with arg3"))).
      getOrElse(log("Some excuse for being unable to work with arg1/arg2"))
    

    它仍然是嵌套的,但至少比上面的模式匹配看起来更优雅。

    或者您可以为此实现自己的 Functor:

    trait Foo[+A] {
      def fmap[B](f: A => B): Foo[B]
    }
    
    case class Bar[A](value: A) {
      def fmap[B](f: A => B): Foo[B] = Bar(f(value))
    }
    
    case object Error[Nothing](message: String) {
      def fmap[B](f: Nothing => B) = Error(message)
    }
    
    def doSomethingWithLotsOfConditions(arg1, arg2, arg3, arg4, arg5) = 
      arg1.get(arg2).fmap(value1 => 
        arg3.get(value1).fmap(value2 => 
          arg4.get(arg5, value2).fmap(value3 => 
            finallyDoSomethingInside))
    
    def processResult = doSomethingWithLotsOfConditions(...) match {
      case Bar(a) => doSomething
      case Error(message) => log(message)
    }
    

    此代码假定 arg.get 返回一个 Foo。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多