Lua支持“尾调用消除(tail-call elimination)”。
尾调用(tail call):当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。例如,下面的代码就是一条“尾调用”:

function f (x) return g(x) end

也就是说,当f调用完g之后就再无其他事情可做了。因此在这种情况下,程序就不需要返回那个“尾调用”所在的函数了。所以在“尾调用”之后,程序也不需要保存任何关于该函数的栈(stack)信息了。当g返回时,执行控制权可以直接返回到调用f的那个点上。有一些语言实现(例如Lua解释器)可以得益于这个特点,使得在进行“尾调用”时不好非任何栈空间。将这种实现称为支持“尾调用消除”。
由于“尾调用”不会耗费栈空间,所以一个程序可以拥有无数嵌套的“尾调用”。例如,在第哦用以下函数时,传入任何数字作为参数都不会造成栈溢出:

function foo (n)
if n > then return foo(n-) end
end

判断当前的调用是一条“尾调用”的准则:一个函数在调用完另一个函数之后,是否就无其他事情需要做了。
例如,下面的代码就不是一条“尾调用”:

function f (x) g(x) end

这个示例的问题在于,当调用完g后,f并不能立即返回,它还需要丢弃g返回的临时结果。类似的,以下所有调用也都不符合上述准则:

return g(x) +         -- 必须做一次加法
return x or g(x) -- 必须调整为一个返回值
return (g(x)) -- 必须调整为一个返回值

在Lua中,只有“return <func>(<args>)”这样的调用形式才算是一条“尾调用”。Lua会在调用前对<func>及其参数求值,所以它们可以是任意复杂的表达式。举例来说,下买的呢调用就是一条“尾调用”:

return x[i].foo(x[j] + a*b , i + j)

[“尾调用” 迷宫游戏示例]
“尾调用”类似一条goto语句。在Lua中“尾调用”的已答应永久是编写“状态机(state machine)”。这种程序通常以一个函数来表示一个状态,改变状态就是goto(或调用)到另一个特定的函数。举一个简单的迷宫游戏的例子来说明这个问题。例如,一个迷宫有几间房间,每间房间中最多有东南西北4扇门。用户在每一步移动中都需要输入一个移动的方向。如果在某个方向上有门,那么用乎可以进入相应的房间;不然,程序就打印一条警告。游戏目标就是让用户从最初的房间走到最终的房间。
这个游戏就是一种典型的状态机,其中当前房间就是一个状态。可以将迷宫中的没见房间实现为一个函数,并使用“尾调用”来实现从一件房间移动到另一间房间。在以下代码中,实现一个具有4间房间的迷宫:

function room1 ()
local move = io.read()
if move == "sourth" then return room3()
elseif move == "east" then return room2()
else
print("invalid move")
return room1() -- stay in the same room
end
end function room2()
local move = io.read()
if move == "sourth" then return room4()
elseif move == "west" then return room1()
else
print("invalid move")
return room2()
end
end function room3()
local move = io.read()
if move == "north" then return room1()
elseif move == "east" then return room4()
else
print("invalid move")
return room3()
end
end function room4()
print("congratulations!")
end

通过调用出世房间来开始这个游戏:

room1()

若没有“尾调用消除”的话,每次用户的移动都会创建一个新的栈层(stack level),移动若干步之后就有可能会导致栈溢出。而“尾调用消除”则对用户已动的次数没有限制。这是因为每次移动实际上都只是完成一条goto语句到另一个函数,而非传统的函数调用。
对于这个简单的游戏而言,或许会觉得将程序设计为数据驱动的会更好玩一点,其中将房间和移动记录在一些table中。不过,如果游戏中的没见房间都有个字特殊的情况的话,采用这种状态机的设计会更为合适。

Lua 正确的尾调用(proper tail call)的更多相关文章

  1. PHP、Lua中的 尾调用

    在程序设计中,递归(Recursion)是一个很常见的概念,合理使用递归,可以提升代码的可读性,但同时也可能会带来一些问题. 下面以阶乘(Factorial)为例来说明一下递归的用法,实现语言是PHP ...

  2. [Lua] 尾调用消除(tail-call elimination)

    <Lua程序设计(第2版)> 6.3 正确的尾调用(proper tail call) Lua是支持尾调用消除(tail-call elimination)的,如下面对函数g的调用就是尾调 ...

  3. JavaScript中的尾调用优化

    文章来源自:http://www.zhufengpeixun.com/qianduanjishuziliao/javaScriptzhuanti/2017-08-08/768.html JavaScr ...

  4. ES6躬行记(15)——箭头函数和尾调用优化

    一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...

  5. ES6学习笔记 -- 尾调用优化

    什么是尾调用? 尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数. function f(x) { return g(x) } 如上,函数 f 的最后一 ...

  6. iOS 的尾调用优化原理

    背景: 今天聊代码规范的问题的时候说了一下尾调用的问题. 一:概念: 什么是尾调用? 尾调用(Tail Call):某个函数的最后一步仅仅只是调用了一个函数(可以是自身,可以是另一个函数). 注意 “ ...

  7. lua报错,看到报错信息有tail call,以为和尾调用有关,于是查了一下相关知识

    尾调用是指在函数return时直接将被调函数的返回值作为调用函数的返回值返回,尾调用在很多语言中都可以被编译器优化, 基本都是直接复用旧的执行栈, 不用再创建新的栈帧, 原理上其实也很简单, 因为尾调 ...

  8. JavaScript 中的尾调用

    尾调用(Tail Call) 尾调用是函数式编程里比较重要的一个概念,它的意思是在函数的执行过程中,如果最后一个动作是一个函数的调用,即这个调用的返回值被当前函数直接返回,则称为尾调用,如下所示: f ...

  9. 深度递归必须知道的尾调用(Lambda)

    引导语 本文从一个递归栈溢出说起,像大家介绍一下如何使用尾调用解决这个问题,以及尾调用的原理,最后还提供一个解决方案的工具类,大家可以在工作中放心用起来. 递归-发现栈溢出 现在我们有个需求,需要计算 ...

随机推荐

  1. applicationContext.xml报错org.springframework.orm.hibernate3.LocalSessionFactoryBean not found

    applicationContext.xml报错org.springframework.orm.hibernate3.LocalSessionFactoryBean not found 解决办法: 1 ...

  2. freetds设置超时

    freetds的超时一般在其配置文件中有设置,实际上程序中也可以设置,动用两个api dbsetlogintime dbsettime 设置在dbopen之前,如下所示: 表示设置登录超时5秒,读写超 ...

  3. 启动其他APK的Activity方法 (转至http://www.cnblogs.com/lijunamneg/archive/2013/02/26/2934060.html)

    有两个app,分别叫做App1和App2.App1包含两个Activity,分别叫做App1_A和App1_B.其中App1_A是入口Activity.也就是App1_A设置intent-filter ...

  4. OpenStack若干概念

    近期在部署OpenStack时涉及到各个服务之间的诸多概念,这里简要记录其中的一些作为备忘. 服务(service) 在OpenStack中,一个服务有若干端点,用户通过端点访问服务并使用服务提供的功 ...

  5. chrome 下改动 agent 的方法

    前言 这篇文章和 tiankonguse 的个人站点里的文章保持同步. 非常早之前,在 chrome 下改动 agent 的方法是使用 chrome 插件. 后来 chrome 的某一个版本号中自带这 ...

  6. 一篇关于apache commons类库的详解[转]

    1.1. 开篇 在Java的世界,有很多(成千上万)开源的框架,有成功的,也有不那么成功的,有声名显赫的,也有默默无闻的.在我看来,成功而默默无闻的那些框架值得我们格外的尊敬和关注,Jakarta C ...

  7. 前端图片压缩(纯js)

    html代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <t ...

  8. Python爬虫学习——光学字符识别

    使用pytesseract库对图片文件(jpg.png.bmp等)进行识别,把图片转换成字符串输出. import pytesseract from PIL import Image img = Im ...

  9. java 对象数组

    java 对象数组 from zhaocundang@163.com 先 用类声明数组: 再把类的实例赋给数组: package works; import java.util.Scanner; pu ...

  10. PCL(Point Cloud Library)的第三方库简单介绍(boost,eigen,flann,vtk,qhull)

    PCL由于融合了大量的第三方开源库,导致学习成本升高~在学习之前我们最好还是了解一下这些库都是干嘛的,以便有的放矢.在之后更好的使用 boost: C++的标准库的备用版,擅长从数学库到智能指针,从模 ...