Scala 匿名函数中的 return
2019-07-08
Q
之前写 Scala 代码在 foreach 上和领导 argue 了一波,原因挺有意思,作文以记之。
大概是这样一段代码
def flatMap(events: Set[String],t: String,collector: Collector[String]): Unit = {events.foreach(f => {if (t.contains(f)) {collector.collect(t)return}})}
events: Set[String] 中存放了一些关键字,然后要判断一下传入的日志 t 是否包含某个关键字,包含的话就把日志 t 发出去,因为这里能保证一条
日志 t 不会同时包含多个关键字,所以为了省一些比较时间这里一旦命中就直接 return 了(当然第一版我用了一个 hasEvent: Boolean 标志位被领导说太费解了所以才换成了 return 2333333),于是产生了这么一波对话
Leader: 是不是写俩 for 循环跟直观一点我: 为啥要俩 for 啊,events 过一遍找到一个满足条件的就直接发出去了吧Leader: 外层的 foreach 然后里面有 return,脑子不太绕的过来 return 是return到什么流程里面。好像现在的写法,return 结束的是当前的 foreach 循环,并不会结束 flatMap.我: scala 里面 for 和 foreach 没差吧,而且 return 这个关键字的语义不是很确定么,就是返回函数值,没哪个语言的 return 在循环内使用是跳出循环的意思吧。
说完最后一句话,心里有点凉,毕竟没啥底气就赶紧查了下,领导的意思是 foreach 的参数是个匿名函数,如下
def foreach[U](f: A => U): Unit =iterator.foreach(f)
匿名函数里的 return 的作用域应该是这个匿名函数内部,所以上面那段代码里的 return 并不能起到 early return 的作用。
道理是这样,而且我好像无法反驳,然而我写了个 demo 发现并不是这样,是能起到 early return 的作用的,如下
object ReturnExample {def main(args: Array[String]): Unit = {assert(run() == "orange")}def run(): String = {val events = Set("apple", "orange", "grape")events.foreach(f => {if (f == "orange") {return f}})"monkey!!!!!!!"}}
所以细化一下就是两个问题了
- Scala
foreach和for的区别是什么? - 匿名函数里的
return到底return到哪儿了?
Why
foreach && for
先说结论[1]
- 简单 for 一个 collection 会在底层转为 collection 上的 foreach 方法调用
- 带 if for 一个 collection 会在底层转为 collection.withFilter.foreach 方法调用
- 带 yield for 一个 collection 会在底层转为 collection 上的 map 方法调用
- 带 if 和 yield for 一个 collection 会在底层转为 collection.withFilter.map 方法调用
这里看一下底层是如何转化的,比如有这么一段代码
class Main {for (i <- 1 to 10) println(i)}
使用 scalac -Xprint:parse Main.scala 可得
$ scalac -Xprint:parse Main.scala[[syntax trees at end of parser]] // Main.scalapackage <empty> {class Main extends scala.AnyRef {def <init>() = {super.<init>();()};1.to(10).foreach(((i) => println(i)))}}
可以看到 for 被转成了 foreach 方法调用,其他的比如带 if 和 yield 的可以自行尝试
匿名函数中的 return
先说结论[2]
return 返回到的是最近的带名方法/函数
最近的带名方法/函数 f 有显示声明的返回值类型,return 表达式进行求值得到值 e,然后把这个值返回给 f, 对上面的 demo 代码编译看看:
→ scalac -Xprint:explicitouter ReturnExample.scala[[syntax trees at end of explicitouter]] // ReturnExample.scalapackage com.xiachufang.spark.job.tarPlatform {object ReturnExample extends Object {def <init>(): com.xiachufang.spark.job.tarPlatform.ReturnExample.type = {ReturnExample.super.<init>();()};def main(args: Array[String]): Unit = scala.Predef.assert(ReturnExample.this.run().==("orange"));def run(): String = {<synthetic> val nonLocalReturnKey1: Object = new Object();try {val events: scala.collection.immutable.Set[String] = scala.Predef.Set().apply[String](scala.Predef.wrapRefArray[String](Array[String]{"apple", "orange", "grape"}));events.foreach[Unit]({final <artifact> def $anonfun$run(f: String): Unit = if (f.==("orange"))throw new scala.runtime.NonLocalReturnControl[String](nonLocalReturnKey1, f)else();((f: String) => $anonfun$run(f))});"monkey!!!!!!!"} catch {case (ex @ (_: scala.runtime.NonLocalReturnControl[String @unchecked])) => if (ex.key().eq(nonLocalReturnKey1))ex.value()elsethrow ex}}}}
可以看到,return 那里被换成了抛出一个 NonLocalReturnControl 异常,然后整个函数顶层有一个大的 try-catch 来捕获这个异常,拿到里面的 value 返回,来靠这种方式实现 early-return
看看别的语言
python: 匿名函数里不支持 return 关键字
java 也是不支持在匿名函数里 return 的,IDE 会报警
private int test() {List<Integer> array = Arrays.asList(1, 2, 3);array.forEach(num -> {return num;});return 100000;}
不过这种写法确实很 confusing,尤其是不同语言之间还有区别,这时候就靠 IDE 和编译器来保证了;私以为这可能也是某种 Scala 不推荐
用 return 关键字的原因吧,比如用标志位来实现
→ scalac -Xprint:explicitouter ReturnExample.scala[[syntax trees at end of explicitouter]] // ReturnExample.scalapackage com.xiachufang.spark.job.tarPlatform {object ReturnExample extends Object {def <init>(): com.xiachufang.spark.job.tarPlatform.ReturnExample.type = {ReturnExample.super.<init>();()};def main(args: Array[String]): Unit = scala.Predef.assert(ReturnExample.this.run().==("orange"));def run(): String = {val events: scala.collection.immutable.Set[String] = scala.Predef.Set().apply[String](scala.Predef.wrapRefArray[String](Array[String]{"apple", "orange", "grape"}));var hasEvent: Boolean = false;var res: String = "";events.foreach[Unit]({final <artifact> def $anonfun$run(f: String): Unit = if (hasEvent.unary_!().&&(f.==("orange"))){res = f;hasEvent = true}else();((f: String) => $anonfun$run(f))});res}}}
就感觉优雅了很多,虽然都是同样的结果,但是从逻辑上来看,没有 early return,也就没有副作用了吧。。。。。。
REF: