理解 Lua 的那些坑爹特性
Lua 那些坑爹的特性
来源 https://blog.lilydjwg.me/2012/12/29/lua-caveats.36879.html协程只能在 Lua 代码中使用
协程(coroutine)应该是 Lua 最大的卖点之一了。可是,它有一个在文档中根本没有提到过的弱点:只能在 Lua 代码中使用,不能跨越 C 函数调用界限。也就是说,从 C 代码中无法直接或者间接地挂起一个在进入这个 C 函数之前已经创建的协程。而 Lua 本身作为一种易于嵌入的语言,必然不时与 C 打交道。
比如以下程序:
C 模块代码:
在官方版 Lua 以及 LuaJIT 中会出现「attempt to yield across metamethod/C-call boundary」错误。只有打过 Coco 补丁的版本才能正常执行。
据说 LuaJIT 已经解决了这个问题,不过我想他们说的是内建函数支持 yield 而已。
在 Lua 5.2 中,提供了新的 API 来支持在 C 中 yield。不过,既然是 C API,当然得改代码,而且看上去比异步回调更复杂。
幽灵一般的 nil
nil 相当于 Python 中的 None 或者 C 中的 NULL,表示「没有这个值」的意思。但是,一个神奇的地方在于,所有未定义的变量的值均为 nil。所以,在 Lua 中有空值 nil,但是有时它又不存在:当你尝试把 nil 值存到表里时,它会消失掉。
另外,当 nil 被传入接受可变参数的函数时,官方版 Lua 只能通过
select('#', ...)获取参数个数。至于 LuaJIT,很遗憾,没有办法。
LuaJIT 中还有这样一个值,它等于 nil。但是根据 Lua 语言标准,只有 false 和 nil 的值为假。于是,在 LuaJIT 中,两个相等的量,却有着不同的真值。它就是 ffi 中的 NULL 指针。
在另外一些地方,也会有其它各种库定义的 null 值,比如
ngx.null、
cjson.null。这些空值之间哪些相等哪些不等就难说了。
没有 continue
Lua 一直不肯添加 continue 关键字。作者声称不添加不必要的特性。请问有谁认为「repeat ... until」结构比「continue」关键字更有必要?于是,凡是本来应当使用 continue 的地方,都不得不弄一个大大的 if 语句:
在 Lua 中只能这么写:
所以,Lua 代码的左边空白的形状都是些 45° 或者 135° 的斜线。
错误信息的表达
Lua 中,习惯的错误表达为,返回两个值,第一个为 nil 表示发生了错误,第二个为字符串,是错误信息。字符串形式的错误信息显示给用户挺不错的(想想微软喜欢的长长的错误号)。可是,程序里只好用模式匹配去判断是否发生了指定类型的错误。这多么像 VimScript 中的错误处理啊。journald 取代 syslog 的重要原因之一就是它存储的是结构化文本。Lua 错误处理最伟大的一点则是我们又回到了字符串匹配。别以为你可以返回一个 table 或者 userdata 来表达错误。很多库可不这么认为。当你的结构化错误被
..连接时你就会发现这厮没救了。
下标
别的编程语言下标都从 0 开始。Lua 为了更「人性化」,其下标从 1 开始。其实写多了也能习惯,除了当通过 ffi 获得一个 C 数组的时候……
提前返回
return 语句之后必须跟着一个
end。于是,很多提前返回的时候只能写
do return end。有意义么?
方法调用
访问表或者 userdata 的域使用一个点
.,连接字符串使用两个点
..。而方法定义和调用时,你需要垂直放置的两个点——冒号
:。它与域访问的一个点相比,也就多了四个像素,显示器不干净或者精神不佳的时候就得小心了!
面向对象
Lua 是不支持面向对象的。很多人用尽各种招术利用元表来模拟。可是,Lua 的发明者似乎不想看到这样的情形,因为他们把取长度的
__len方法以及析构函数
__gc留给了 C API。纯 Lua 只能望洋兴叹。
结论
Lua 只适合写写配置。做纯计算用用 LuaJIT 也不错。复杂的逻辑还是交给专业点的语言吧。
理解 Lua 的那些坑爹特性
来源 http://sw.is-programmer.com/2013/1/3/understand-lua-caveats.36905.html按:最近看到了依云的文章,一方面,为Lua被人误解而感到十分难过,另一方面,也为我的好友,依云没有能够体会到Lua的绝妙和优雅之处而感到很遗憾,因此我写了这篇文章,逐条款地说明了依云理解中出现的一些问题。希望能够帮助到大家!
1. 协程只能在Lua代码中使用
是的,协程在当你需要挂起一个C函数的时候无法使用。但是,在提出这个缺陷的时 候,是不是应该想一想:为什么Lua会有这个缺陷? 原因很简单:这一点完全避不开,这是C的问题,C无法保存函数执行的某个现场用于 返回这个现场继续执行,因此完全没有办法在Lua的协程被唤醒的时候,回到这个现场。 那么怎么办呢?Lua5.2做出了很优秀的设计。既然无法回到C的现场,那么我们不回 去了,而是采取“事件通知”的方式告诉你,“hey哥们,你前面的逻辑被切了,想办法 补救吧”,这就是所谓的CPS——继续风格的编程。继续在这里是一个Scheme/Lisp术 语,意思是“当前的操作执行完了以后,下面该做什么?”这种风格是Lua能支持任意 Yield 的必要条件。在C的限制下,只有这一种方法能突破这个限制。 至于你说的“比异步回调更复杂”,我想你弄混了两点:1.这只是C API层面的修改 ,完全不影响到Lua代码层面,你的Lua代码完全不必做出任何修改,而且,你对 coroutine的用法完全错了!等会儿我会教你coroutine到底怎么用。2.上面提到了, 这是唯一一种能支持coroutine的方式,既然是唯一一种,就无所谓复杂与否了。3. 我下面会演示给你,为什么说coroutine完全解放了程序员,使用coroutine的代码会带来 革命性的简化。 我们分两步来说明这个问题:第一步,我们先来看你的例子:你想做的事情是,在执 行 c.callback的时候,能够yield出来,继续其他的流程。这里必须要说明,你的API设 计本身就是callback式的,因此这种API本身就犯不着coroutine,Lua本身能完全地处理 。这里我会给出一个支持coroutine的C模块设计,让这个模块能支持coroutine,第二步 ,我会告诉你coroutine实际上是用在什么方面的,是如何取代事件回调机制的。在完成 这个说明后,我们来说明coroutine到底有什么好处,为什么说coroutine比事件回调机制 有着 革命性的优秀之处。 你的例子是这样的: ?2. 幽灵一般的 nil
我不否认,在我刚刚学习Lua的时候,我的确被nil坑过很多遍。我们先抛弃掉luaJIT 关于NULL设计的问题(这个设计本身也是一种无奈,而且LuaJIT毕竟并不能完全继承Lua 作者对Lua的理念),先来看看nil究竟是什么——从nil中,我学习到了,在遇到坑爹特 性之前,先不要急着抱怨,想想为什么作者会设计这么坑爹的特性。要么作者是比你低能 的傻逼,要么这么设计就的确是有充分的考虑和不得已的苦衷的。这点你想到过吗? nil是一个表示“没有”的值。是的,就是真的“没有”,因此nil本身就是一个幽灵 ——它除了表示“这里没有东西”以外,没有其他的任何含义!它不是None(None是一个 表示“空”的对象),它也不是NULL(NULL表示没指向任何地方的指针——总所周知指针 本身必定是有值的,哪怕那个值是NULL)。Lua的作者十分聪明的将“没有”这个概念也 引入了语言,并且还保持了语言的一致性:请问,将“没有”存入一个表里面,它如果不 消失,还能发生什么事呢? 那么如何表示“空”或者“没有指向任何地方的引用”呢?两个办法,你可以存入 false,或者可以用下面这个巧妙的方法: ?3. 没有continue
是的,Lua一直不肯加入continue。为什么呢?因为repeat until。而为什么强调“ 不添加不必要的特性”的Lua作者会舍弃掉“那么常见”的continue,却保留不那么常见 的repeat呢?是Lua的作者傻么? 不是。这是经过仔细设计的。我先告诉你答案,再仔细地分析:事实上,在加入 repeat以后,continue是逻辑上不可能实现的,而repeat语句实现了一个用其他的特性完 全无法取代的特性。 注意看repeat的结构: repeat <block> until <exp> 问题就在<exp>上了。Lua规定,<exp>是在<block>的作用域下计算的!这也就意味着 : ?4. 错误信息的表达
我只想说一句话:其实大多数在预见到会对错误进行处理的场合里面,错误的返回方 式其实并不是nil, errmsg,而是nil, errmsg, errno。别的你懂了。5. 下标
参看 novelties-5.2.pdf,说的非常明白了。6. 提前返回
这是一个语法问题,事实上return语句不跟着end的话,那么编译器就根本无法编译 return语句了。这是Lua“行无关语法”的一个必然折衷,我开始也不爽,但事实是,在 我数万行的Lua开发中,除了测试必要要注释一部分代码以外,我根本没用过do return end这种表达——至于为什么,你实际开发一下就知道了:因为这种代码一定会导致完全 无法被执行到的死代码。7. 方法调用
8. 面向对象
这两点恰好就是Lua的优势啊!!有时间我会写一篇文章来讨论。这实际上是Lua能以 比其他语言小巧灵活得多地去处理复杂逻辑的一个必然原因了。这里只说一点:Lua5.2中 ,表所具有的元方法已经和C API能处理的完全一样多了。纯Lua已经不必对着__len和 __gc而望洋兴叹了。 关于这一点,MikePall(LuaJIT实现者)还专门和Lua作者吵了一架,因为让表支持 __gc会导致luaJIT的jit编译非常难写= =||||9. 结论
我开始学习Lua的时候,也几乎得到了跟你一样的结论。然而,在长达两年的Lua开发 中,我逐渐认识到了Lua的美,认识到了Lua实现的优雅和严谨。现在如果有新手想学习C 语言开发的诀窍和技巧,我通常会建议他去拜读Lua的C实现源代码。Lua的实现太优雅了 。而Lua的设计也凝聚着作者的一点一滴的心血。Lua精准绝妙的设计是Lua强大的表达能 力的表现。继续学习下去吧,我向你保证,你一定会发现,Lua实际上脚本语言里面表达 能力最强,概念最统一,设计最优雅的语言了。Lua无愧脚本语言之王! Category: 未分类 | Tags: Lua | Read Count: 36861 评论 (11) 1 [回复] 依云 说:5 年前
关于协程,我当然知道协程该怎么用。Lua C API 确实有些细节上不太清楚,文档太简略了。pcallk 只能解决一次 yield 吧?如果 yield 的次数不定该怎么办?我有个库的函数,在运行过程中可能需要调用一个回调函数来取某些数据。在 Lua 绑定中,这个回调函数就是调用一个 Lua 函数,然后由于涉及网络操作,它是会 yield 不定次数的。
你说的所有这些,要么是 LuaJIT 2.0.0 还没实现的特性(LuaJIT 比 Lua 快太多了),要么是要求作者对 Lua 该怎么编程很熟悉(如果我接手的那些代码是你写的就好了)。至于表达能力,还是不要太强的好,不然每个人的错误返回方式和面向对象的实现都不一样,概念是统一了,实现千差万别、各不相容。
没错,「do return end」就是调试时用的。
2 [回复] 亚弥 说:5 年前
@依云: 恩,说句实话,只看reference的确很难搞明白k系列函数内部的核心思想。我是一开始就跟着邮件列表的讨论才比较清楚的。不过你真的可以看看novelties-5.2.pdf,这里面有很详细的说明。
另外不明白“一次yield”和“多次yield”有什么区别。只要用了k系列函数,你多少yield都没问题的,因为Lua自己会帮你维护Lua内部yield时候的状态。无论你如何yield,回到C层面(即从内部的coroutine返回)只会有一次,因此k系列函数一定能做到你想要的,而且并不需要特别的设计。
你仔细看看LuaJIT,很多特性已经实现了,包括goto。k系列函数没实现是基于两个原因:1.LuaJIT关注纯Lua应用,甚至用ffi库取代了C API的必要性;2.LuaJIT因为与Lua作者的巨大分歧(邮件里面吵了好几架),所以不打算实现5.2兼容了。至少短期内是不想的。sigh……快的话,其实快不了多少,只是科学计算方面的确快了很多,如果你的代码是C模块密集的,那么LuaJIT很难提高效率,其次是如果你用了NYI的特性,那么也是不会快的(比如字符串模式匹配和coroutine),从我的经验看,网游逻辑书写用luaJIT对效率的提升不大,甚至可能比原Lua更慢。
表达能力问题的确是个双刃剑,但有个朋友说得好“做得到总比做不到好”,这个就看怎么解读了。
错误返回是有标准模式的,文章里面提到了newtry/protect模式,不过写到后来写忘了= =有时间补上吧,OO的话也是有标准模式的,而且是两套。关键是,因为底层概念统一,所以即使是千差万别的实现,最终也一定是兼容的。你如果处理过实现之间的纠葛就会体会到底层概念统一带来的巨大好处。
do return end的话也就是一个词和三个词的区别吧……sigh……就当多打字了,实在不行做个imap或者iab呗……
5 年前
哇,偶像你也在这里!!
4 [回复] Larry Xu 说:5 年前
newtry/protect??
luasocket中的那套,我当时看了也觉得蛮有意思的
可否补充介绍下实际过程的使用方式
5 年前
你好,能给我lua邮件列表的邮箱吗?谢谢~~
6 [回复] wtyqm 说:4 年前
您好,想问下,如果想在pcall里使用coroutine,有什么办法嘛? 文中的protect,指的就是luaSocket里那种封装pcall的方式吧
7 [回复] 太阳神上 说:4 年前
我觉得 lua 还有一个坑爹特性,table 当哈希表时,无法以 O(1) 的时间复杂度取得其元素个数。
8 [回复] 荒野无灯 说:4 年前
从lily那里过来的。看了你这文章,受益颇多。
9 [回复] skyblue 说:4 年前
求解释coroutinize中这部分的必要性
local cur = current
current = nil
3 年前
local a = true
repeat a = false until a == false
这样吧
3 年前
关于continue的例子,我猜楼主的意思是这样吧?
少写了一个local?
local a = true
repeat
local a = false
until a
- 理解 Lua 的那些坑爹特性
- 理解 Lua 的那些坑爹特性
- Lua 那些坑爹的特性
- 【深入Lua】理解Lua中最强大的特性-coroutine(协程)
- 游戏人生(一),我的lua之旅:那些坑爹的CCBReaderLoad
- 【深入Lua】理解Lua中最强大的特性-coroutine(协程)
- 游戏人生(一),我的lua之旅:那些坑爹的CCBReaderLoad
- 【深入Lua】理解Lua中最强大的特性-coroutine(协程)
- 理解java的三大特性之多态
- 理解C# 3.0新特性之Extension方法浅议
- 理解java的三大特性之多态
- 深入理解Lua与C数据通信的栈
- cocos-Lua设置热更搜索路径的理解(看公司代码的一些小想法)
- LUA中的协成特性
- QL语句通常不是很容易理解,特别是你阅读别人已经写好的语句。因此,很多人指出我们应该遵循在其他语言中遵循的原则,像加上注释和功能模块化。我最新注意到一个很多人都没有使用的Postgres关键特性,也就
- 比较:是为了更好的理解它的特性
- 理解C# 3.0新特性之Extension方法浅议
- 深入理解Java虚拟机JVM高级特性与最佳实践阅读总结——第二章 Java内存区域与内存溢出异常
- webstorm那些好用的特性
- Lua: 好的, 坏的, 和坑爹的