Scala 嵌套函数有效吗?
在Scala或Lua等编程语言中,我们可以定义嵌套函数,例如Scala 嵌套函数有效吗?,scala,lua,functional-programming,Scala,Lua,Functional Programming,在Scala或Lua等编程语言中,我们可以定义嵌套函数,例如 function factorial(n) function _fac(n, acc) if n == 0 then return acc else return _fac(n-1, acc * n) end end return _fac(n, 1) end 由于每次调用外部函数时都会定义或创建嵌套函数实例,这种方法是否会导致效率低下?我不知道lua,但在Scala中非
function factorial(n)
function _fac(n, acc)
if n == 0 then
return acc
else
return _fac(n-1, acc * n)
end
end
return _fac(n, 1)
end
由于每次调用外部函数时都会定义或创建嵌套函数实例,这种方法是否会导致效率低下?我不知道lua,但在Scala中非常常见,并在递归函数中使用,以确保尾部安全优化:
def factorial(i: Int): Int = {
@tailrec
def fact(i: Int, accumulator: Int): Int = {
if (i <= 1)
accumulator
else
fact(i - 1, i * accumulator)
}
fact(i, 1)
}
def阶乘(i:Int):Int={
@泰勒克
定义事实(i:Int,累加器:Int):Int={
如果(i)
由于嵌套函数
每次调用外部实例时,都会定义或创建实例
功能
效率是一个大而广泛的话题。我假设“低效”的意思是“每次递归调用该方法是否会产生开销”
我只能代表Scala发言,特别是针对JVM的风格,因为其他风格可能会有不同的表现
我们可以把这个问题分成两部分,这取决于你真正的意思
Scala中的嵌套(局部作用域)方法是一种词法作用域特性,这意味着它们为您提供了对外部方法值的可访问性,但一旦我们发出字节码,它们就在类级别上定义,就像普通java方法一样
为了完整性,您要知道Scala也有函数值,它们是第一类公民,这意味着您可以将它们传递给其他函数,因为它们是使用类实现的,所以这些函数会产生分配开销
Factorial可以用尾部递归的方式编写,就像您在示例中编写的那样。Scala编译器足够智能,它会注意到您的方法是尾部递归的,并将其转换为迭代循环,避免每次迭代调用方法。如果可能,它还可以尝试内联Factorial
方法,避免了额外堆栈帧分配的开销
例如,在Scala中考虑下面的阶乘实现:
def factorial(num: Int): Int = {
@tailrec
def fact(num: Int, acc: Int): Int = num match {
case 0 => acc
case n => fact(n - 1, acc * n)
}
fact(num, 1)
}
表面上看,我们有一个递归方法。让我们看看JVM字节码是什么样子的:
private final int fact$1(int, int);
Code:
0: iload_1
1: istore 4
3: iload 4
5: tableswitch { // 0 to 0
0: 24
default: 28
}
24: iload_2
25: goto 41
28: iload 4
30: iconst_1
31: isub
32: iload_2
33: iload 4
35: imul
36: istore_2
37: istore_1
38: goto 0
41: ireturn
我们在这里看到的是递归变成了一个迭代循环(a+a跳转指令)
关于方法实例的创建,如果我们的方法不是尾部递归的,JVM运行时将需要为每次调用解释它,直到发现它足够,这样它将JIT编译它,并在以后为每次方法调用重新使用机器代码
通常,我会说,除非您注意到该方法正在执行热路径,并且分析代码导致您提出这个问题,否则您不应该对此感到担心
总而言之,效率是一个非常微妙的、特定于用例的主题。我认为我们没有足够的信息来告诉您,从您提供的简化示例来看,这是否是您的用例的最佳选择。我再次指出,如果这不是您的分析器上显示的内容,请不要担心这一点。答案取决于当然是语言
特别是在Scala中,内部函数被编译,因为它们位于定义它们的函数范围之外
通过这种方式,该语言只允许您从中定义它们的词法范围调用它们,但实际上并不多次实例化该函数
我们可以通过编译
第一个是Lua代码的一个相当忠实的端口:
class Function1 {
def factorial(n: Int): Int = {
def _fac(n: Int, acc: Int): Int =
if (n == 0)
acc
else
_fac(n-1, acc * n)
_fac(n, 1)
}
}
第二个函数大致相同,但尾部递归函数的定义不在factorial
的范围内:
class Function2 {
def factorial(n: Int): Int = _fac(n, 1)
private final def _fac(n: Int, acc: Int): Int =
if (n == 0)
acc
else
_fac(n-1, acc * n)
}
我们现在可以使用scalac
编译这两个类,然后使用javap
查看编译器输出:
javap -p Function*.scala
这将产生以下输出
Compiled from "Function1.scala"
public class Function1 {
public int factorial(int);
private final int _fac$1(int, int);
public Function1();
}
Compiled from "Function2.scala"
public class Function2 {
public int factorial(int);
private final int _fac(int, int);
public Function2();
}
我添加了private final
关键字,以最小化两者之间的差异,但需要注意的是,在这两种情况下,定义都出现在类级别,内部函数自动定义为private
和final
,并带有一个小装饰以确保没有名称类(例如,如果在两个不同的函数中定义了一个循环
内部函数)
不确定Lua或其他语言,但我可以预期至少大多数编译语言都会采用类似的方法。让我们在Lua中使用嵌套函数或不使用嵌套函数对其进行基准测试
变量1(每次调用时都会创建内部函数对象)
变量2(函数未嵌套)
基准测试代码(计算12!
10百万次并以秒为单位显示已用CPU时间):
Lua 5.3(解释器)的输出
LuaJIT(JIT编译器)的输出
是的(或者曾经是),当执行多次通过函数定义时,Lua努力重用函数值就是明证
函数值之间的相等性已更改。现在,函数
定义可能不会创建新值;它可能会重用某些以前的值
如果新函数没有明显差异,则为
由于您已编码(假设Lua)分配给在更高范围内声明的全局或局部的函数,因此您可以自己对短路进行编码(假设没有其他代码将其设置为除nil
或false
之外的任何值):
对于不同的语言,答案可能会有所不同。我希望Scala不会在每次调用外部函数时都重新定义函数;这只是一个名称空间问题。对于Lua,答案很可能是每次都会重新定义函数。这是否真的有区别?嗯。测量一下。请使用一个没有rec的示例Urson澄清了这个问题不是关于尾部递归的。我不明白为什么这么多人把这个问题作为关于(尾部)递归的问题来回答,这个问题已经清楚地说明了。请注意,在这种情况下,内部函数不使用任何外部函数变量。因此,它完全是eq
local function factorial1(n)
local function _fac1(n, acc)
if n == 0 then
return acc
else
return _fac1(n-1, acc * n)
end
end
return _fac1(n, 1)
end
local function _fac2(n, acc)
if n == 0 then
return acc
else
return _fac2(n-1, acc * n)
end
end
local function factorial2(n)
return _fac2(n, 1)
end
local N = 1e7
local start_time = os.clock()
for j = 1, N do
factorial1(12)
end
print("CPU time of factorial1 = ", os.clock() - start_time)
local start_time = os.clock()
for j = 1, N do
factorial2(12)
end
print("CPU time of factorial2 = ", os.clock() - start_time)
CPU time of factorial1 = 8.237
CPU time of factorial2 = 6.074
CPU time of factorial1 = 1.493
CPU time of factorial2 = 0.141
function factorial(n)
_fac = _fac or function (n, acc)
…
end
…
end