用于理解和功能创建的数量

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倍以上的函数.

我检查了语言规范,但仍然无法理解答案.有人可以向我解释一下吗?

kir*_*uku 8

简而言之:

示例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循环

for ( p <- e; p' <- e' ... ) e''
Run Code Online (Sandbox Code Playgroud)

where ...是一个(可能是空的)生成器,定义或保护序列,被转换为

e .foreach { case p => for ( p' <- e' ... ) e'' }
Run Code Online (Sandbox Code Playgroud)

现在当写一个函数文字时,每次需要调用函数时都会得到一个新实例(§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完成的优化.此外,您的示例包含在每次迭代时执行的系统调用 - 这可能是最昂贵的操作,超过其他所有操作.