首页 >工作总结 > 内容

Lua: 好的, 坏的, 和坑爹的

2023年9月22日 00:21

在我使用Lua编程整整9个月后, 是时候停下来反省一下这段经历了. 过去了几年里, 我使用了各式各样的语言:Perl (soaplite.com, 还有其它的项目, 包括我现在的咨询工作), C (DHCPLiteping-pong juggling robot), JavaScript (Google Maps相关经验 和 canvas),MATLAB(ping-pong juggling robot), 等等, 从Turbo Pascal 到 F# -- 比较一下Lua和我接触过的其它语言是一件非常好玩的事情. 我已经使用Lua完成了一些不同类型的项目: 一个远程调试器(MobDebug), 扩展了一个LuaIDE(ZeroBrane Studio), 一个移动应用程序 (LuaRemote), 一些教育性的脚本 (EduPack), 还有一个使用Lua在浏览器画板上进行绘图demo.

虽然我已经见过很多提到Lua的好和坏的列表 (例如,Lua的优势,为什么使用Lua,为什么Lua没有得到广泛地应用,Lua的优点,Lua的好和坏,Lua对比JavaScript, 还有Lua的陷阱), 但是有些特性坑死爹了, 还有些他们忘了提, 所以我就自己搞了个列表. 虽然这说不上非常专业, 也没有覆盖到语言的每个方面 (如 math 和string 库), 但这是根据我的编程语言经历得出来的.

好的

  • 小巧:20000行C代码可以编译进182K的可执行文件 (Linux下).
  • 可移植: 只要是有ANSIC 编译器的平台都可以编译. 你可以看到它可以在几乎所有的平台上运行:从microcontrollersLego MinstormsNXT, 到移动平台, 到游戏主机,甚至浏览器(翻译成JavaScript).
  • 作为一个嵌入式可扩展语言提供了简单直接的C/C++交互接口.
  • 足够快:见与其它语言的性能比较,还有一个JIT编译器可以显著地提高多数任务的性能; 对于那些仍然对性能不满意的人, 可以把关键部分使用C实现, 然后与其集成, 这样还可以享受其它方面的好处.[3/9/2013更新]替换已经消失的结果为benchmarksgame.
  • 文档完善:参考手册,书籍,wiki,6页的简短参考等.
  • 友好和热情的社区. 在杰出的文档, wiki,邮件列表, 和StackOverflow中,没有什么问题没有我找不到答案的.
  • 适合初学者和非程序员的简洁语法. Lua 从Modula(Pascal的分支, 已经广泛应用于教育做为教学语言)借鉴了多数的控制语法. 我现在仍然记得早期使用过Philippe Kahn的快速而优雅的Turbo PascalIDE.
  • 集成的解释器:只需要在命令行下运行lua.
  • 先天的协程支持, 用于实现迭代器非抢占式多线程.
  • 低延迟的增量垃圾回收, 没有额外的内存开销, 低实现复杂度, 并且支持weak tables.
  • 强大并多样化的可以保存任意类型的数据(除了nil) , 还可以使用任意类型的值进行索引 (除了nil):{1, 2, 5, foo = "bar", [func] = "something", ["some spaces"] = value()}.
  • 词法作用域.
  • 一流的函数闭包支持的函数式编程.
  • 尾调用:return functioncall().
  • 递归函数不需要事先声明:local function foo() ... foo() ... end; 注意这样不行local foo = function() ... foo() ... end.
  • 函数返回多个值:return 1, 2, 3. 调用者可以认为返回值是任意个数的: 如果多于3个, 其余会被丢弃; 如果少于3个, 那其它的会是未初始化的nil.
  • 函数允许变化的变量个数,function foo(...) local args = {...}; bar(param, ...) end.
  • Table可以 "拆包" 成参数列表,unpack(或Lua 5.2的table.unpack):print(unpack({1, 2, 3}))打印1 2 3.
  • 操作环境变量 (Lua 5.1中的getfenvsetfenvLua 5.2中的_ENV操作), 此外还可以构造沙盒.
  • 同时赋值多个变量:local a, b, c = 1, 2,x, y = y, x, ora, b = foo().
  • 多行字符串 (using[[...]]; 可以使用[[...[=[...]=]...]])包含和注释 (--[[...]]).
  • 可选的分号语句分隔符 (多数用于解决模棱两可的的情况a = f; (g).x(a)).
  • 重载使用metatables.
  • 元编程可以根据你的DSL修改抽象语法树来创造新的语法.
  • for语句有两种形式:generic(使用迭代器:for a in iter() do ... end) 和numeric(使用数字:for a = 1, 5, 0.1 do ... end); 数字的这个支持各种类型的步进 (不仅仅是整数).
  • 函数调用的语法糖 (f'string',f"string",f[[string]], andf{table})和方法调用(obj:m()).
  • 简单而强大的调试库.

与众不同的

  • 表和字符串索引从1而不是0开始.
  • 对一个表中的值赋nil会从表中删除它. 这就是说对于不存在的值返回nil, 所以元素存不存在跟它是不是nil是同一个问题.a = {b = nil}产生一个空表.
  • 没有独立的整数类型;数字类型表示的是实数.
  • 没有类;面向对象使用函数实现; 继承使用metatable机制实现.
  • 方法调用使用object:method(args)的写法, 与object.method(object, args)的写法是等价的, 但object只取值一次.
  • nilfalse是仅有的表示假的值; 0, 0.0, "0" 等其它的一切值都是true.
  • 不等于是 ~= (例如,if a ~= 1 then ... end).
  • not, or, and操作符是逻辑运算符.
  • 赋值是语句, 这就意味着没有a=b=1if (a=1) then ... end的写法.
  • 没有a+=1,a++, 或其它简写形式.
  • 没有continue语句, 尽管有一个解释和一堆的替代品, 如在循环中使用repeat break until true跳出 或者使用一个Lua 5.2中的goto语句.
  • 没有switch语句.
  • 某些上下文可能会用到括号; 例如,a = {}; a.field正常, 但{}.field不行; 后者需要这样写({}).field.
  • 循环的控制变量默认是局部的, 循环完了就没了.
  • for循环中的极限和步进值是缓存过的; 这意味着for i = init(), limit(), step() do ... end中的三个函数init,limit, 和step只在循环开前调用过一次.
  • 条件和其它控制语言不需要括号.
  • 字符串和数字会自动转换 (需要一个数字时提供一个字符串, 反之亦然), 除了相等比较:0 == "0"false,{} ~= 1true, 还有foo["0"]foo[0]引用的是表中不同的值; 其它关系运算符会在比较不同类型的值时产生错误.
  • 逗号和分号都可以作为表中的元素分隔符; 也同样都可以作为可选的分隔符放在结束括号前:a = {a = 1, b = 2, }.
  • 比想像中还要少的内部组件; 可能一些人觉得这就像 "电池没有包含在内"一样. 从另一个角度来看, 这成就了它的紧凑而又可移植的核心, 不过同时有一些库可以进行补偿, 如LuaRocksPenlight.

坏的

  • 有限的错误处理支持(使用pcallxpcall),尽管有些人争论这已经够用了, 只需要加一些语法糖和特性支持(如确定性的finalizer).pcallerror的组合十分强大, 特别是error可以返回任何东西(例如一个表)而不是仅仅是一个字符串, 但是catch ... finally结构在多数情况下可能更加清晰直观.
  • 默认是全局的作用域 (这么说对Lua 5.2不公平, 它已经没有全局了). 有一个strict模块要求所有全局变量都需要初始化. 虽然我并没有很多问题是由未初始化的全局变量引起的, 但还是把它放到"坏的"分类, 因为有一次我犯了一个错误, 在调用一个"next"变量时没有局部化它,引起一个问题就是迭代器覆盖了另一个模块的next函数.
  • 没有Unicode 支持(最起码string.len和模式识别函数需要识别 Unicode 字符); 不过有一个ICU库的绑定实现了Unicode支持. 可以看一下这条消息和后续总结的的关于现有的支持和string.*需要什么样的修改.
  • 有限的模式匹配支持, 尽管已有的也十分强大.在使用了15 年Perl后, 我非常想念其中的一些正则表达式特性(多数是前向搜索, 可选组(group )?, 还有组内组), 没有任何一个都是会增加实现复杂度的. 对于需要更强大的正则表达式的人可以使用LPeg和它的re模块.
  • 没有三目运算符;有一些替代品. 一般我使用foo = test and value1 or value2形式, value2testvalue1都为false时可以赋值.
  • 没有内置POSIX函数. 虽然有luaposix模块, 但是它需要编译, 这并不是一个好的选择. 尽管对于这个我并没有很强的需求, 但是每当我需要获取/设置一个环境变量时总会直观想到去访问getenvsetenv[6/1/2012更新]miko 在评论中提到, 有os.getenv, 但是没有相应的os.setenv.
  • 没有类/对象 finalizer. Lua 通过__gc metamethod提供finalizer 的功能, 但它只能用于自定义类型 (不是表), 并且不能跟其它语言的相应功能匹配, 举例来说,Perl中的DESTROYEND方法.[05/27/2012更新]Lua 5.1中有一个没有文档说明的newproxy特性, 它实现了表的finalizers; Lua 5.2 移除了这个特性的同时增加了表的__gc元方法.
  • 没有Lua和C代码之间的yielding:coroutine.yield在跨越 Lua/C 边界调用时会失败attempt to yield across metamethod/C-call boundary. 我在使用luasocket和协程进行异步编辑时多次遇到过这个错误, 最后使用copas模块解决. 在Lua 5.2中这个问题得到解决.

坑爹的

  • 表中元素的个数并不是很容易获取, 结果取决于你怎么做 (或你怎么定义"长度"). 这可能不是个意外, 因为Lua提供了强大的表并支持灵活的索引方式 (数字或其它Lua类型, 除了nil). Lua中的表有两部分: "数组" 部分(使用t = {1, 2, 3}生成) 和 "哈希" 部分(使用t = {a = "foo", ["b"] = 2}生成); 这两者可以灵活地结合在一起.#table返回最短的"数组"部分长度(没有任何缺口) 而table.maxn(t)返回最长的"数组" 部分(Lua 5.2移除了这个函数). "哈希" 部分没有定义长度. 两者都可以使用pairs方法进行遍历, 同时允许你对其中的元素进行计数. 然而,print(#{1, 2, nil, 3})打印4 却不是想像中的 2 ,print(#{1, 2, nil, 3, nil})打印的则是2. 我确信有一个合理的理由解释它, 但是现在说是就是"坑爹"的地方.[11/17/2012更新]FireFly 在评论中提到, Lua 5.2 中表的长度只定义成没有洞的.
  • return必须是语句块中的最后一句; 也就是说,function foo() print(1); return; print(2) end会触发一个错误'end' expected...unexpected symbol near <whatever statement you have after 'return'>(这取决于在return之后有没有分号). 没有人会这样写, 除非你在调试, 但我却被它坑了好几次. 原本我想把它放进"与众不同的"分类, 但是我发现它前后矛盾. 在一个不能使用return的地方却能使用do return end.[5/19/2012更新]这同样出现在break语句上, 虽然在Lua 5.2中break不再必须是语句块的最后一句了.
  • 函数只返回一个值但它并不是列表中的最后一个; 如:
      function f123() return 1, 2, 3 end  function f456() return 4, 5, 6 end  print(f123(), f456()) -- prints 1, 4, 5, 6  print(f456(), f123()) -- prints 4, 1, 2, 3
    这个return的行为也受到这条规则约束:return f456()返回3个值, 但return (f456())只返回一个值(注意多出的括号). 关于这个语言特性有很好的文档, 但我仍然认为它太坑爹了 (或许在旁人看来它是优点).

总的来说, 到目前为止我很享受这个语言带来的简洁和便利, 尽管有些东西跟我之前的做法有点不一样. 特别是在8岁的儿子很快地学会了Lua的语法后, 我觉得自己的那些关于 Turbo Pascal 的经历已经过时了.


参考文章:https://blog.csdn.net/xoyojank/article/details/12762909

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时候联系我们修改或删除,在此表示感谢。

特别提醒:

1、请用户自行保存原始数据,为确保安全网站使用完即被永久销毁,如何人将无法再次获取。

2、如果上次文件较大或者涉及到复杂运算的数据,可能需要一定的时间,请耐心等待一会。

3、请按照用户协议文明上网,如果发现用户存在恶意行为,包括但不限于发布不合适言论妄图

     获取用户隐私信息等行为,网站将根据掌握的情况对用户进行限制部分行为、永久封号等处罚。

4、如果文件下载失败可能是弹出窗口被浏览器拦截,点击允许弹出即可,一般在网址栏位置设置

5、欢迎将网站推荐给其他人,网站持续更新更多功能敬请期待,收藏网站高效办公不迷路。

      



登录后回复

共有0条评论