Lua 正确的尾调用(proper tail call)
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)的更多相关文章
- PHP、Lua中的 尾调用
在程序设计中,递归(Recursion)是一个很常见的概念,合理使用递归,可以提升代码的可读性,但同时也可能会带来一些问题. 下面以阶乘(Factorial)为例来说明一下递归的用法,实现语言是PHP ...
- [Lua] 尾调用消除(tail-call elimination)
<Lua程序设计(第2版)> 6.3 正确的尾调用(proper tail call) Lua是支持尾调用消除(tail-call elimination)的,如下面对函数g的调用就是尾调 ...
- JavaScript中的尾调用优化
文章来源自:http://www.zhufengpeixun.com/qianduanjishuziliao/javaScriptzhuanti/2017-08-08/768.html JavaScr ...
- ES6躬行记(15)——箭头函数和尾调用优化
一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...
- ES6学习笔记 -- 尾调用优化
什么是尾调用? 尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数. function f(x) { return g(x) } 如上,函数 f 的最后一 ...
- iOS 的尾调用优化原理
背景: 今天聊代码规范的问题的时候说了一下尾调用的问题. 一:概念: 什么是尾调用? 尾调用(Tail Call):某个函数的最后一步仅仅只是调用了一个函数(可以是自身,可以是另一个函数). 注意 “ ...
- lua报错,看到报错信息有tail call,以为和尾调用有关,于是查了一下相关知识
尾调用是指在函数return时直接将被调函数的返回值作为调用函数的返回值返回,尾调用在很多语言中都可以被编译器优化, 基本都是直接复用旧的执行栈, 不用再创建新的栈帧, 原理上其实也很简单, 因为尾调 ...
- JavaScript 中的尾调用
尾调用(Tail Call) 尾调用是函数式编程里比较重要的一个概念,它的意思是在函数的执行过程中,如果最后一个动作是一个函数的调用,即这个调用的返回值被当前函数直接返回,则称为尾调用,如下所示: f ...
- 深度递归必须知道的尾调用(Lambda)
引导语 本文从一个递归栈溢出说起,像大家介绍一下如何使用尾调用解决这个问题,以及尾调用的原理,最后还提供一个解决方案的工具类,大家可以在工作中放心用起来. 递归-发现栈溢出 现在我们有个需求,需要计算 ...
随机推荐
- applicationContext.xml报错org.springframework.orm.hibernate3.LocalSessionFactoryBean not found
applicationContext.xml报错org.springframework.orm.hibernate3.LocalSessionFactoryBean not found 解决办法: 1 ...
- freetds设置超时
freetds的超时一般在其配置文件中有设置,实际上程序中也可以设置,动用两个api dbsetlogintime dbsettime 设置在dbopen之前,如下所示: 表示设置登录超时5秒,读写超 ...
- 启动其他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 ...
- OpenStack若干概念
近期在部署OpenStack时涉及到各个服务之间的诸多概念,这里简要记录其中的一些作为备忘. 服务(service) 在OpenStack中,一个服务有若干端点,用户通过端点访问服务并使用服务提供的功 ...
- chrome 下改动 agent 的方法
前言 这篇文章和 tiankonguse 的个人站点里的文章保持同步. 非常早之前,在 chrome 下改动 agent 的方法是使用 chrome 插件. 后来 chrome 的某一个版本号中自带这 ...
- 一篇关于apache commons类库的详解[转]
1.1. 开篇 在Java的世界,有很多(成千上万)开源的框架,有成功的,也有不那么成功的,有声名显赫的,也有默默无闻的.在我看来,成功而默默无闻的那些框架值得我们格外的尊敬和关注,Jakarta C ...
- 前端图片压缩(纯js)
html代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <t ...
- Python爬虫学习——光学字符识别
使用pytesseract库对图片文件(jpg.png.bmp等)进行识别,把图片转换成字符串输出. import pytesseract from PIL import Image img = Im ...
- java 对象数组
java 对象数组 from zhaocundang@163.com 先 用类声明数组: 再把类的实例赋给数组: package works; import java.util.Scanner; pu ...
- PCL(Point Cloud Library)的第三方库简单介绍(boost,eigen,flann,vtk,qhull)
PCL由于融合了大量的第三方开源库,导致学习成本升高~在学习之前我们最好还是了解一下这些库都是干嘛的,以便有的放矢.在之后更好的使用 boost: C++的标准库的备用版,擅长从数学库到智能指针,从模 ...