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. 访问网站出现EOF

    HTTP/0.0 503 Service Unavailable Date: Tuesday, 18-Apr-17 10:29:46 CST Keep-Alive: timeout=38 EOF 今天 ...

  2. pymysql.err.InternalError: (1054, "Unknown column 'None' in 'field list'")

    错误提示: Traceback (most recent call last): File "D:/projectwc/test/dd.py", line 43, in <m ...

  3. 【转】【Python】Python多进程与多线程

    1.1 multiprocessing multiprocessing是多进程模块,多进程提供了任务并发性,能充分利用多核处理器.避免了GIL(全局解释锁)对资源的影响. 有以下常用类: 类 描述 P ...

  4. SpringBoot系列六:SpringBoot整合Tomcat

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合 Tomcat 2.背景 SpringBoot 本身支持有两类的 WEB 容器:默认的 To ...

  5. CI框架 -- 在视图文件中使用 PHP 替代语法

    如果你不使用 CodeIgniter 的 模板引擎, 那么你就只能在视图文件中使用纯 PHP 语法了.为了精简视图文件, 使其更可读,建议你在写控制结构或 echo 语句时使用 PHP 的替代语法. ...

  6. Linux Shell nohup命令用法

    linux的nohup命令的用法.   在应用Unix/Linux时,我们一般想让某个程序在后台运行,于是我们将常会用 & 在程序结尾来让程序自动运行.比如我们要运行mysql在后台: /us ...

  7. C# 校验Email(电子邮件)地址是否合法

    用于校验给定的Email地址是否合法,只针对用于提供的Email地址的格式,不对其是否真实存在进行校验. /// <summary> /// 验证EMail是否合法 /// </su ...

  8. 转载:AOP那点事

    原作者:黄勇 博客地址:https://my.oschina.net/huangyong/blog/161338 又是一个周末,刚给宝宝喂完牛奶,终于让她睡着了.所以现在我才能腾出手来,坐在电脑面前给 ...

  9. u3d资源打包只能打包场景材质,不能打包脚本

    GameObject附带脚本打包进来以后,只有一个脚本名字,估计只是关联了脚本,内容却不在,所以后面打包资源的话,一定要把脚本,shader之类的,放到工程目录下

  10. AOP-配合slf4j打印日志

    基本思想 凡在目标实例上或在目标实例方法(非静态方法)上标注自定义注解@AutoLog,其方法执行时将触发AOP操作: @AutoLog只有一个参数,用来控制是否打印该方法的参数和返回结果的json字 ...