IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    scala雾中风景(27): lambda表达式里的return是抛异常实现的

    hongjiang发表于 2016-11-17 20:36:19
    love 0

    尽管Scala和其他函数式编程都不太鼓励使用return,但我不喜欢太多缩进的代码,所以很少用较多的逻辑嵌套(除非是那种非常对称的嵌套),而是喜欢将不满足条件的先return掉。最近遇到一个scala里的流控陷阱,即在lambda里的return背后实际是通过特定的异常来实现的。

    对于流控和异常捕获之前也遇到过其他陷阱,但稍不留意仍可能重犯,比如下面这段代码:

    try {
        ...
        for (topicMeta <- resp.topicsMetadata; partMeta <- topicMeta.partitionsMetadata) {
          if (topicMeta.topic == p.topic && partMeta.partitionId == p.partition) {
            redisClient.hset(key, field, node.host + ":" + node.port)
            return
          }
        }
    } catch {
        case e: Throwable =>
        ...
    }

    它是一段在actor里的逻辑,因为不希望非预期异常导致这个actor重启,所以是对Throwable进行的捕获,然而运行时竟捕获到了scala.runtime.NonLocalReturnControl$mcV$sp这样的异常。for语法糖容易让人忘记它里面的操作可能是一段匿名函数,简化一下这个例子:

    ➜  cat Test.scala
    object Test {
        def main(args: Array[String]) {
            val list = List("A", "B", "C")
            for (e1 <- list) {
                if (e1 == "C") {
                    println("ok, do something.")
                    return
                }
            }
        }   
    }

    看看它编译的中间环节:

    ➜  scalac -Xprint:explicitouter Test.scala
    [[syntax trees at end of             explicitouter]] // Test.scala
    package <empty> {
    object Test extends Object {
    def <init>(): Test.type = {
      Test.super.<init>();
      ()
    };
    def main(args: Array[String]): Unit = {
      <synthetic> val nonLocalReturnKey1: Object = new Object();
      try {
        val list: List[String] = immutable.this.List.apply[String](scala.this.Predef.wrapRefArray[String](Array[String]{"A", "B", "C"}));
        list.foreach[Unit]({
          @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction1[String,Unit] with Serializable {
            def <init>(): <$anon: String => Unit> = {
              $anonfun.super.<init>();
              ()
            };
            final def apply(e1: String): Unit = if (e1.==("C"))
              {
                scala.this.Predef.println("ok, do something.");
                throw new scala.runtime.NonLocalReturnControl$mcV$sp(nonLocalReturnKey1, ())
              }
            else
              ()
          };
          (new <$anon: String => Unit>(): String => Unit)
        })
      } catch {
        case (ex @ (_: scala.runtime.NonLocalReturnControl[Unit @unchecked])) => if (ex.key().eq(nonLocalReturnKey1))
          ex.value$mcV$sp()
        else
          throw ex
      }
    }
    }
    }

    很明显return在嵌套的匿名函数里是无法跳出外层函数的,所以编译器通过抛出 scala.runtime.NonLocalReturnControl 异常来实现跳出最外层。所有的lambda中使用return都是如此:

    def main(args: Array[String]) {
        val lambda: String => Unit =
            (str: String) => { if ("hit" == str) return } //跳出main
    
        try {
            lambda("hit")
        } catch {
            case e: Throwable => //scala.runtime.NonLocalReturnControl
            e.printStackTrace()
        }
    }

    还是要注意对Throwable的捕获,不要干扰流控逻辑:

    try{
        ...
    }catch {
    case e0: ControlThrowable => throw e0 // 不要干预流控的异常
    case e1: Throwable => e1.printStackTrace
    }   


沪ICP备19023445号-2号
友情链接