1
4
2013
22

多返回值:Lua 又一坑

本文来自依云's Blog,转载请注明。

假设myfunc已经在其它地方定义,你觉得以下两段代码作用一样吗?

local t = {}
local item = myfunc()
table.insert(t, item)
item = nil
local t = {}
table.insert(t, myfunc())

代码一把函数的结果存放在临时变量里再作为参数传给其它函数,代码二直接将函数返回值作为参数传给其它函数。看上去,代码二比代码一简短了一些,少用了个变量名。

可是,如果myfunc返回多个值的话,代码二将不能正确运行,因为myfunc所有返回值均会传递table.insert。和其它语言完全不一样。我想,这个应该是「真正的」多返回值吧?——它返回的不是像其它语言那样的是一种复合类型的值,而真真正正的是多个值,以一种超乎直觉的方式存在着。

所以,要么总是通过赋值来指定需要的返回值,要么不要给已有函数增加新的返回值。我倾向于前者,因为,你记得你所有用到的函数的返回值数目吗?

Category: 编程 | Tags: Lua | Read Count: 14466
maplebeats 说:
Jan 04, 2013 09:48:00 PM

Orz,这颠覆了我直觉= =!

cc 说:
Jan 07, 2013 08:42:12 AM

函数的返回值变了, 也算是接口改变吧, 接口变了, 那么用到的地方都要改变也算是合理的吧. 照这样说, python里也是存在这个坑的.

Avatar_small
依云 说:
Jan 07, 2013 12:20:40 PM

Python 又不能真正地返回多个值。函数的参数是可以根据需要增加的,返回值也差不多,除了上边提到的情况。这样子多返回值的用处就更少了,还不如像 Python 那样返回一个包含多个值的对象呢。

最重要的是,这样子太违反直觉了!

maplebeats 说:
Jan 07, 2013 12:27:41 PM

python没有这个坑呀。。。返回的是一个tuple

Avatar_small
依云 说:
Jan 07, 2013 01:08:10 PM

Python 那是返回一个值——一个 tuple。Lua 当然也可以学 JavaScript 返回个 table。

Avatar_small
亚弥 说:
Jan 08, 2013 05:17:17 PM

 

评心而论,这的确是个坑儿,当初掉在这里面爬都爬不起来……是的,跟多返回值有关的最后一个坑儿我直到学习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, ...)
....
    end,
}
 
这种代码你能忍受?
 
你学的是Lua,那么就容忍Lua的个性吧。你要明白,个性和缺陷是两码事。被坑是正常,但是你一定要说清楚,这是Lua特性而不是Lua缺陷。这正是当你怒火下去,仔细想想,却发现虽然违反直觉,但却是最佳的设计的地方。这种事情经历久了,每次你再被坑,你就会想着“肯定是我哪个直觉被某语言惯坏了!Lua肯定有更好的方案了”然后去验证,如果的确是Lua的问题就写邮件到列表,而不是直接发篇文章说“LuaTMD又坑我啦!!!雅蠛蝶!”
 
恩,就是这样~
 
Avatar_small
亚弥 说:
Jan 08, 2013 05:23:07 PM

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

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

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

Avatar_small
依云 说:
Jan 08, 2013 05:56:33 PM

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

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

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

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

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

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

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

Avatar_small
亚弥 说:
Jan 08, 2013 06:59:02 PM

已经无力吐槽了……

先说最重要的吧,资料问题。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,拼错了还安安静静的
过去了”,你说我该如何吐槽……

Avatar_small
依云 说:
Jan 08, 2013 07:56:01 PM

是的,我没有看到 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 了,因为不知道一个模块是从哪个文件引入的,调试麻烦。

Avatar_small
亚弥 说:
Jan 08, 2013 08:12:19 PM

……第二份文档,即《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,我擦啊这描述害死我了……

cc 说:
Jan 09, 2013 09:43:50 AM

在python里如果也有这么一句:
table.insert(t, myfunc())

如果一开始myfunc返回1个值, 但后面改为返回多个值了, 这逻辑就完全不对了嘛, 严重的话, 程序也是会崩溃的, 为啥这里就不算坑了.

cc 说:
Jan 09, 2013 09:47:28 AM

后面有点杠上了的意思

Avatar_small
依云 说:
Jan 09, 2013 12:19:19 PM

Python 永远只返回一个值,只是它可能是一个 tuple 而已。
Lua 中,a = func() 永远取 func 返回的第一个值,而 Python 中它总是取 func 返回的*那个值*,而不会因为它返回了一个 tuple 就去取那个 tuple 的第一个值。

cc 说:
Jan 09, 2013 12:38:13 PM

哦 是这个意思啊
但在这个语境下, *一个*值和*多个*值 就不用纠结了吧

自由建客 说:
Jan 09, 2013 03:33:01 PM

这点确实不好。若是我设计,我会默认如 python 用非拆分方式,提供另一种语法或选项做 lua 这样的拆分方式。
惰性求值在的确违反直觉,要惰性求值的应该是提供过程参数,或者表达式参数,总之是要明确说明才好。

Avatar_small
亚弥 说:
Jan 09, 2013 05:33:13 PM

现在的问题是,非拆分是要有成本的。成本就是会产生新对象。Lua的设计思路是“除非用户明确写出来,不然不会产生任何内存损耗”。

Mike 说:
Feb 14, 2013 06:59:55 PM

Common Lisp 黨表示 cl 會在多返回值時自動選擇第一個返回值,其餘返回值必須手動提取。

荒野无灯 说:
Nov 03, 2013 04:25:03 AM

好吧,我看到那一句“超乎直觉的方式存在着”亮了

JunkFood 说:
Nov 03, 2013 11:28:51 AM

matlab的nargin(可变参数)和nargout(可变返回值)才是真正的反直觉
甚至还有这样的写法:
if nargout == 1
do xx
else
do yy
end

unmage 说:
May 07, 2014 03:16:38 PM

这个 本来 就不建议 直接把 func() 作为 参数 传递
最好用 local p = func()
然后 foo(p) 使用
自己不理解多返回值,还在这喷
你要是确定的 使用 单一 元素
你就写 foo((func()))

chk 说:
Sep 10, 2014 01:01:59 AM

这特性其实很好,我也纠结了好久,但是后来发现返回值是可以pack的,像文中的坑其实相比python好多了,如果一条语句不能按照期望来执行,抛出错误无疑是最好的结果。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com