Scala 嵌套函数有效吗?

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中非

在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中非常常见,并在递归函数中使用,以确保尾部安全优化:

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