1

多返回值:Lua 又一坑

 2 years ago
source link: https://blog.lilydjwg.me/2013/1/4/multiple-return-value-another-caveat-in-lua.36934.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
多返回值:Lua 又一坑
亚弥 说: 9 年前

评心而论,这的确是个坑儿,当初掉在这里面爬都爬不起来……是的,跟多返回值有关的最后一个坑儿我直到学习Lua一年以后才从里面爬出来……(好吧,其实是被文档给误导了……) 但是,不管怎么说,依云,你写这样的文章难道不怕误导么?违反直觉?Haskell用正规序求值你怎么不说违反直觉?Python没有类型你怎么不说违反直觉?关键就是,(虽然说起来有点强词夺理,我知道),这就是Lua的直觉啊! 在Lua的世界里,函数的返回值和函数参数一样,其数量都是接口的一部分啊!你没有看到就连标准库的函数说明都是采用[+,-,m]的形式说明的么……前者是参数数量,后者是返回值数量啊!
还有,栈是Lua独有的特色。无论是对其的操作还是其在语言中的应用。Python的那个无论如何效率上无法赶上Lua(参数tuple object什么的,参数dict object什么的……返回值tuple object什么的)。我记得我开始也抱怨过,可是!你学的不就是Lua么?如果Lua没有一点变化,那么你学的难道不是披着Lua语法皮的Python么?那我们为什么学这么一门披着别人皮的语言呢?
很多人,动不动说自己精通多少多少语言,其实他们只精通一门语言,去年夏天在华为看到的“把Lua当作Java写”深深的刺激了我。我随便举个例子:为了模拟枚举值,他们居然这样:
Options = {
    OPT_THIS_IS_A_OPT = "this is a opt",
然后在用的地方这样:
    SendXXXXMessage(Options.OPT_THIS_IS_A_OPT, ....)
——老大直接传字符串会死吗?这是Lua啊!!!!
还有函数只在表里面写,如果一个文件只有一个函数,居然都用这种用法写:
XXXXPackage = {
    Main = function(self, ...)
这种代码你能忍受?
你学的是Lua,那么就容忍Lua的个性吧。你要明白,个性和缺陷是两码事。被坑是正常,但是你一定要说清楚,这是Lua特性而不是Lua缺陷。这正是当你怒火下去,仔细想想,却发现虽然违反直觉,但却是最佳的设计的地方。这种事情经历久了,每次你再被坑,你就会想着“肯定是我哪个直觉被某语言惯坏了!Lua肯定有更好的方案了”然后去验证,如果的确是Lua的问题就写邮件到列表,而不是直接发篇文章说“LuaTMD又坑我啦!!!雅蠛蝶!”
恩,就是这样~
亚弥 说:
9 年前

好吧,我其实知道这个是娱乐向什么的。
其实就是希望文章的背后,在抱怨过后,能冷静的思考为什么Lua要这样抽风。这样文章至少不是纯娱乐向,而且,这样的文风很容易误导人……给人一种“Lua本来就不如Python”的感觉。
Lua语言短小精悍,API清晰,速度快;Python语法优雅,库大而全,速度某些情况优化甚至比Lua还好。这两门语言本来就是完全不同的思路,完全不同的应用方向,完全不同的设计理念,这怎么比……

说起Python,就想说它的import this,完全是说一套做一套……表面衣着光鲜弘扬正义,背地里咋地咋地。其实那些简单的概念,都是由复杂的概念+语法糖搞出来的。Lua的语法糖非常少。其真正优雅的概念很多都是概念本身,既然是本身,自然就得向概念的一些东西妥协——因为已经涉及到原则方面的改变了。

作为思考题,我问问楼主:请问Lua为什么要这么设计?单纯只是为了坑你?

依云 说:
9 年前

看了半天 Lisp,所谓「正则序」在 Haskell 中就是表现为懒惰求值是吧?说实话,这个对于我来说并不违反直觉,只是和其它很多*编程语言*不同罢了。代数运算里经常这么用,而且我一开始学习 Haskell 便被告知这么个奇特的特性。是的,Lua 一上来也说了函数的多返回值,但是并没有说明将返回直接作为参数传递时的行为。

说 Python「没有类型」并不准确。动态类型而已。我既没有看到它与我的任何已有知识不一致,也没看到它有什么特别的地方。我学习的第一门语言是 QBasic,第二门是 JavaScript。需要声明类型的 C/C++ 是后来才学的。

「违反直觉」的同义语是「与已知的类似的情况都不一样」。我已经知道好些语言了,Lua 函数返回这种情况之前从没听说过。

「你没有看到就连标准库的函数说明都是采用[+,-,m]的形式说明的么」——我这些天老在翻文档,刚刚又去检查了一遍,还真没发现这个。

至于 Lua 这样设计的缘由,恐怕得深入 Lua 虚拟机,了解它的函数调用的实现才能知晓。可是很遗憾,我用的是 LuaJIT,它使用了一种我看不懂也找不到文档的汇编语法。

写这篇文章,只是简单地记录,也让对 Lua 有兴趣的人预先知道有这么回事。毕竟,Lua 的相关资料太少,文档也不如 Python 的那样给力(很多 Python 初学者会掉进的坑、难以理解的设计都在文档里有说明)。

另外,对于选项不直接传递字符串是有好处的——拼写错误更容易发现,特别是当你使用了静态语法检查工具的时候。另外它可以方便地维护。因为定义在同一地方,所以你很容易地看出有哪些选项。要修改选项对应的值也只需要更改一处(字符串的全局替换容易误伤,特别是项目大导致有同名不同类选项的时候)。

亚弥 说:
9 年前

已经无力吐槽了……

先说最重要的吧,资料问题。Lua的资料就两份,只有两份!一份是官网的
manual,lua.org/manual/5.2,严格意义上这一个单个的网页已经完全涵盖了
Lua的所有内容了。你不需要深入虚拟机,Lua的3.4.9节已经说得非常清楚了,
而且为了方便理解还带了例子,除非你根本连函数调用这一节都没看,不然不可
能没印象的。多返回值其实有其他的坑,但不是这个地方,这个地方是说的很明
白的。

如果嫌英文的不爽,没问题,云风有这份文档的中文翻译,请向codingnow.com
伸手。不过还有另一个问题,就是这毕竟是manual,看起来很枯燥无味,那么也
行,另一份Roberto写的文档《Programming in Lua》总可以了吧?有中文版。2
我相信大多数人看的也是这个版本吧?

Programming in Lua第5章《函数》,在“多返回值”这一节就已经直接提到了
多返回值可以放在函数参数列表的最后一个扩展。我不明白“提到多返回值但是
没提到扩展”是什么情况,Lua两份标准文档都没有这个问题。

理解这个并不需要理解Lua的**实现**,LuaJIT和Lua是完全不同的实现,但为什
么在这方面行为很像?因为这是Lua的**特性**。这是多返回值带来的一个功能
!要去掉这个功能非常容易,Lua和LuaJIT都可以只做少量修改就能去除这个功
能。另外,涉及到语法方面,Lua和LuaJIT的代码十分相近,而且跟汇编毫无关
系,你清楚LuaJIT的原理吗?还是只凭着你那不堪被违背的直觉在乱说?

Lua**只有**这两份标准文档,听起来很少?但Lua就只需要这么少。这两份文档
已经涵盖了标准Lua的任何方面了。其他的都是第三方的库,这也是Lua容易学习
的巨大优势。就怕那些连文档都不看完就开始搞的人。

对了,网上的《Programming in Lua》的中文版,是luachina.org在06年经过
Roberto授权翻译的,这份文档描述的是Lua5.0,和六年后的Lua5.2有着较大差
别,所以,买正版书吧。或者网上有英文的第二版pdf也可以看。

这里只是说明你一些最基本的错误概念。下面逐条回复你上面提出的问题。

什么叫“违背直觉”?我开始学Basic的时候是小学五年级,第一个违背直觉的
说法是赋值,i=i+1是合法的。这个违背直觉到了现在,已经成了我的一种本能
,举这个例子是要说明,违背直觉什么的东西,未必就是不合理的。量子力学里
面违背直觉的地方大把。

我不知道你是否详细学习过Haskell,我可以告诉你Haskell的坑比Lua密集至少
一百倍,我自己学过几年,我也有同学学过,你真的以为“看上去”正则序咋地
咋读就真的能绕过去?很多时候你掉到坑里才发现那是坑!

JavaScript也有违背直觉的地方:函数内部任意地方声明的变量,从函数开头就
可以用了,所谓“JS特色函数作用域”,违背直觉么?很多时候为了一个作用域
,js都得写(function(){...})(),这是有多么无奈啊……

Python没有类型是我的错,我的确是指动态类型,你不会以为我连这个都不知道
吧= =我的意思是,对C来讲,动态类型就是违背直觉的,你仔细想,一个int能
塞进去一个struct,而且还能完整的取回来!这种事情和i=i+1已经差不了多少
了。我就不相信你以前就没跳过Python的坑(bound/unbound method还记得么亲
,__new__和__init__,__getattr__和__getattribute__还分得清楚么?)。你
理解的动态类型,是以其实现来理解的,你觉得实现简单(一个指针加一个tag
嘛)你才觉得符合直觉。但我理解它是从原理上理解的,对
Name/Variable/Value的思考,这样让我明白了它和C 在设计理念上的根本区别

再说说VimL,里面的坑无以计数,我最喜欢跳的坑是忘记了函数参数的a:前缀,
其余的请自行脑补。

如果你的脑子只能转过一道弯,那么那些智者千虑的结果你只能以为是违背直觉
的。伴随着你的进步,你的直觉会越来越准确,这才是学习的正道。

每个语言都有坑。每个语言都会有违背直觉的地方,如果“所有的事情都与已知
的情况一样”,那么要么就是个马甲/方言,要么就是个积木货。我不是为Lua找
借口,我是希望你在发现问题的时候,先自己思考一下是不是自己的学习有疏漏
,而别把问题一股脑扔到语言身上,说语言坑人。这也是我将解答相关的内容放
在最上面的原因。

坚持用下去吧,Lua违背直觉的地方还有更多。慢慢的你就会习惯,并开始理解
为什么Lua要这么违背直觉。你就会被训练出Lua的直觉,你就会觉得Lua好了。
很多牛人都玩乐器。乐器最开始都违背直觉(G和弦?人类的手怎么可能按得到
……横跨五个品格?这不可能!),我现在已经很少说这些话了,因为以前觉得
不可能的事情一件一件被我完成,所以没有什么是不可能的。我现在只觉得吉他
很舒服,品格弦踞都恰到好处,但我绝不会忘记刚刚学习的时候想把它砸掉的冲
动。我会记得,是我慢慢在适应这种乐器,而不是这种乐器一开始就这样迎合我
的生理结构。

最后说一句,不要想当然,那个项目没有任何静态动态工具,他们这么做只是为
了和Java像而已。甚至放着require不用,自己用Lua写个功能类似的函数叫
import,这是图那般呢。要看选项,可以有更简单的方式,比如在需要选项的函
数定义的时候,就可以通过DSL的方式说明选项。这方面我写过的代码太多了。
这里只是说明那些人直接是把Lua当作Java在用。这么做在那个项目里面除了多
打字,完全没有任何好处,甚至因为这个引入过无数次Bug,然后他们说Lua不好
:“Java都有类型检查,变量拼错了会提示,你看看Lua,拼错了还安安静静的
过去了”,你说我该如何吐槽……

依云 说:
9 年前

是的,我没有看到 3.4.9 节,因为这些文档是 5.2 才有的。我的系统都前些天才有 5.2 版本。LuaJIT 也没有跟进。

关于汇编,我是指 LuaJIT 的实现的代码。LuaJIT 是我唯一需要考虑的实现。而且 LuaJIT 和 Lua 的特性并不完全一致。所以我不愿去看 Lua 的实现。

我从来没有说 Lua 这个特性是「不合理」的。

Python 也是函数范围的作用域。JavaScript 写成匿名函数调用是为了避免污染全局空间,因为它没有一个广泛支持和认可的模块机制。

「bound/unbound method」我还真不记得,因为我没怎么学/用 Python 2.x。我说过,我的入门语言不是 C 族。JavaScript 和 Python 一样是动态语言,所以真要说违反直觉,那违背我直觉的也是静态类型语言。是的,Python「变量」应当叫「name」而不是「variable」。这和生活经验是一致的——名字与类型无关。我不是通过其实现来理解动态类型的。

我从来没有责怪 Lua 的意思。我当然知道每种语言都有自己的坑。自己遇到了,掉进去了,就作个标记,提醒自己和以后看到的人。

PS: 对于把 Lua 当作 Java 写的人,你还指望他们写 DSL?
PPS: 我也想重新发明个 require 了,因为不知道一个模块是从哪个文件引入的,调试麻烦。

亚弥 说:
9 年前

……第二份文档,即《Programming in Lua》,是5.0就有的,创作年代应该是03年左右,5.2是12年出的(还是11年快过年的时候??)那个时候是绝对有这些文档的。

5.1的manual在此:http://www.lua.org/manual/5.1/,还是希望你仔细阅读。5.1的manual网上有很多中文翻译,你找找,但记得要仔细,因为有一些是5.0的……不兼容的确很恼火,但这也是Lua生命力的特征,忍了吧。

5.1的manual对多返回值的叙述更加靠前,而且非常显眼:在“2.5 表达式”这一章的最开头一段。

对了,有些常识应该知道,LuaJIT和Lua二进制兼容(ABI层面),也就是说,你可以直接用LuaJIT的lua51.dll代替Lua的,而不会引起任何问题(包括C模块),而且LuaJIT严格执行了Lua5.1标准(即本帖说的manual)。LuaJIT与Lua语义上的唯一不同是ffi带来的,一些cdata的默认行为在plain Lua里面无法通过第三方库的方式做到,除此以外没有任何不兼容。

要说不合理的话,的确是有点点不合理,这里面是有点地方有问题,不过不在这里。我还是提醒你一声,免得你掉进新的洞里 = =一个是执行顺序和赋值顺序,一个是可能有0长度的explist,这两个看到注意一下,我掉进去过很多次了= =我当初看的文档是5.0的,都说function如果不写return最后会return一个nil,我擦啊这描述害死我了……


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK