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)
		
引导语 本文从一个递归栈溢出说起,像大家介绍一下如何使用尾调用解决这个问题,以及尾调用的原理,最后还提供一个解决方案的工具类,大家可以在工作中放心用起来. 递归-发现栈溢出 现在我们有个需求,需要计算 ...
 
随机推荐
- 第三百三十七节,web爬虫讲解2—PhantomJS虚拟浏览器+selenium模块操作PhantomJS
			
第三百三十七节,web爬虫讲解2—PhantomJS虚拟浏览器+selenium模块操作PhantomJS PhantomJS虚拟浏览器 phantomjs 是一个基于js的webkit内核无头浏览器 ...
 - (转)LCD:LCD常用接口原理篇
			
关键词:android LCD TFT TTL(RGB) LVDS EDP MIPI TTL-LVDS TTL-EDP平台信息:内核:linux2.6/linux3.0系统:android/a ...
 - T4生成多文件时,不生成自己
			
如:我用的网上的生成多文件的一个include文件. 生成多文件时,默认会生成一个以自己名字命名的文件如: 有一个demo.tt文件,生成时会出来一个demo.cs文件(默认情况下) 解决方法: Fo ...
 - MySQL谨慎使用"replace into"
			
From: http://blog.xupeng.me/2013/10/11/mysql-replace-into-trap/ MySQL 对 SQL 有很多扩展,有些用起来很方便,但有一些被误用之后 ...
 - CSS3 3D立方体翻转菜单实现教程
			
今天我们来看一个非常有创意的CSS3 3D菜单,这个菜单的菜单项是可以旋转的长方体,鼠标滑过是长方体即可旋转,看看下面的效果图,是不是感觉非常酷,我觉得这个菜单很适合用在咱们开发人员的个人网站上. 当 ...
 - JavaSE(二)之继承、封装、多态
			
学习完类与对象终于认识到什么是类,什么是对象了.接下来要看的就是java的三大特征:继承.封装.多态. 一.封装(数据的隐藏) 在定义一个对象的特性的时候,有必要决定这些特性的可见性,即哪些特性对外部 ...
 - asp.net网页中添加年月日时分秒星期。
			
html代码如下: 现在是<span id="TimeSpan"></span> <script type="text/javascript ...
 - Gridview中的选择、删除、编辑、更新、取消留着备用。
			
后台程序: public partial class tw2 : System.Web.UI.Page{ protected void Page_Load(object sender, Even ...
 - YFCMF 问题
			
1.菜单不见了,yf.php (main 改为0 ) function tagMenu $parseStr .='echo get_menu("main","'.$to ...
 - iOS模拟(糟糕的)网络环境
			
有时候为了模拟在糟糕的网络环境下app的表现,会故意拔网线(断wifi),苹果其实提供了专门的工具来精确地模拟你在几个预设的场景下的网络连接情况:Network Link Conditioner 点击 ...