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.scala
package <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.scala
package 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()
else
throw 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.scala
package 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: