gor*_*ral 14 scala for-comprehension
最近我接受了Scala Developer职位的采访.我被问到这样的问题
// matrix 100x100 (content unimportant)
val matrix = Seq.tabulate(100, 100) { case (x, y) => x + y }
// A
for {
row <- matrix
elem <- row
} print(elem)
// B
val func = print _
for {
row <- matrix
elem <- row
} func(elem)
Run Code Online (Sandbox Code Playgroud)
问题是:哪种实施方式A或B更有效?
我们都知道,理解可以转化为
// A
matrix.foreach(row => row.foreach(elem => print(elem)))
// B
matrix.foreach(row => row.foreach(func))
Run Code Online (Sandbox Code Playgroud)
B可以写成 matrix.foreach(row => row.foreach(print _))
据说正确答案是B,因为A会创建print100倍以上的函数.
我检查了语言规范,但仍然无法理解答案.有人可以向我解释一下吗?
简而言之:
示例A在理论上更快,但实际上您不应该测量任何差异.
答案很长:
正如你已经发现的那样
for {xs <- xxs; x <- xs} f(x)
Run Code Online (Sandbox Code Playgroud)
被翻译成
xxs.foreach(xs => xs.foreach(x => f(x)))
Run Code Online (Sandbox Code Playgroud)
这在§6.19SLS中有解释:
一个for循环
Run Code Online (Sandbox Code Playgroud)for ( p <- e; p' <- e' ... ) e''where ...是一个(可能是空的)生成器,定义或保护序列,被转换为
Run Code Online (Sandbox Code Playgroud)e .foreach { case p => for ( p' <- e' ... ) e'' }
现在当写一个函数文字时,每次需要调用函数时都会得到一个新实例(§6.23SLS).这意味着
xs.foreach(x => f(x))
Run Code Online (Sandbox Code Playgroud)
相当于
xs.foreach(new scala.Function1 { def apply(x: T) = f(x)})
Run Code Online (Sandbox Code Playgroud)
引入本地函数类型时
val g = f _; xxs.foreach(xs => xs.foreach(x => g(x)))
Run Code Online (Sandbox Code Playgroud)
您没有引入优化,因为您仍然传递函数文字foreach.实际上代码较慢,因为内部foreach被转换为
xs.foreach(new scala.Function1 { def apply(x: T) = g.apply(x) })
Run Code Online (Sandbox Code Playgroud)
其中一个额外的apply方法调用g发生.但是,您可以在写作时进行优化
val g = f _; xxs.foreach(xs => xs.foreach(g))
Run Code Online (Sandbox Code Playgroud)
因为内在foreach现在被翻译成
xs.foreach(g())
Run Code Online (Sandbox Code Playgroud)
这意味着函数g本身被传递给foreach.
这意味着B在理论上更快,因为每次执行for comprehension的主体时都不需要创建匿名函数.但是,上面提到的优化(函数直接传递给foreach)不适用于理解,因为规范说翻译包括函数文字的创建,因此总是创建不必要的函数对象(这里我必须说编译器也可以对其进行优化,但事实并非如此,因为对于理解的优化是困难的,并且在2.11中仍然没有发生.总而言之,这意味着A更有效,但如果没有理解的话,B会更有效(并且没有为最内层函数创建函数文字).
然而,所有这些规则只能在理论上应用,因为在实践中有scalac的后端和JVM本身都可以进行优化 - 更不用说由CPU完成的优化.此外,您的示例包含在每次迭代时执行的系统调用 - 这可能是最昂贵的操作,超过其他所有操作.