lua的设计目标是嵌入式语言,所以和其它动态语言(如python、ruby)相比其自带的库缺少很多实用功能。
好在有lua社区有Penlight,为lua提供了许多强大的功能,接下来的几篇博客,我会简译Penlight的wiki。
目的
常有人说lua不带电池。因为lua的目标是可以运行在各种机器上的简洁语言,(有些机器甚至不
支持布尔系统)。lua类似于操作系统内核(注:即不是完整的系统,只有基本功能),lua的作者
并没有把围绕lua开发完整的生态系统看做自己的职责。这是社区的角色。
软件设计的原则是识别通用模式,并复用。这不但让代码更易管理,而且更易阅读。
Penlight使用了许多编码原则,这样你的代码变得更简洁。如在Lua里通常用{unpack(t)}复制
表,但这只对“小”表有用而且不健壮。因此Penlight提供了tablex.deepcopy(),它可以用来复制
嵌套的表和相关联的元表,所以可以用它复制复杂对象。
lua默认采用这样的错误处理:如果参数类型错误,抛出错误;如果有问题,返回nil,message。
也有些例外的函数,如input.fields默认是返回message并立即关闭程序(可以改变默认方式)。
对脚本而言,与提供堆栈跟踪相比,这是更合适的方式。词法函数为了简化编码,总是返回错误,
必须用pcall封装。
如果你习惯Python的约定,注意lua里的索引从1开始。
lua不建议用table.foreach,而是泛型for,但是使用Penlight提供的高阶函数,它在某些情况下
特别有用。注意tablex.foreach,颠倒了顺序,先传值,之后索引。虽然乖张,符合预期要更好。
Penlight唯一外部依赖是 LuaFileSystem (lfs ),如果你想dir.copyfile在Windows上成功工作
,你需要 alien 或 LuaJIT 。(如果失败,会调用等效的shell命令)
注入还是不注入
人们很早就意思到大的程序里需要保证命名清晰的方法,如tables (Lua), namespaces (C++)
或modules (Python)。在大公司里很容易遇到命名冲突,在一些简单的语言,如Lua,并没有提
供像C++那样解决命名冲突的详细机制,因此更易遇到。在一小圈朋友里‘Bruce‘是唯一的,不需
要加姓。这取决于你写十多行的脚本,还是上万行的程序。
Penlight提供了正规和非正规两种方式,你可以随意使用。
正规方式:
local utils = require 'pl.utils'
utils.printf("%s\n","hello, world!") 非正规方式:
require 'pl'
utils.printf("%s\n","That feels better")
require ‘pl’
让所有的Penlight模块可用,而不需要单独调用。
正规方式在写模块时更好。
Andrew Starks贡献了另一种方法,很好的在两种方法里取得了平衡。
require'pl.import_into'返回一个函数,它有一个表参数,把Penlight库导入,如果参数为空,
返回一个新的。
local pl = require'pl.import_into'()
pl表是一个 ‘lazy table’,即按需载入,因此可以使用pl.utils.printf,这样不会污染全局。
如果你在lua5.2里使用 _ENV来定义模块,要用Penlight可以这么做:
local _ENV,M = require 'pl.import_into' () function answer ()
-- all the Penlight modules are available!
return pretty.write(utils.split '10 20 30', '')
end return M
默认把Penlight放在_ENV里,可能会对模块造成意外效果(和 module(…,package.seeall)一样
)
。为了方便和安全,你需要向这个函数传入true,这样module M就和_ENV不一样,只包含导出的
函数。(注:对lua5.2不了解,这句话不解)
你想把pl.stringx里的函数导入到标准库里,可以这么做:
require 'pl'
stringx.import()
或者,更快的:
require('pl.stringx').import() 更好的方法师把表导入到局部环境,这样可以让名称简介。
> require 'pl'
> utils.import(math) --把math里的函数直接导入到全局
> = sin(1.2)
0.93203908596723
当第一次require来导入时,utils.import 的参数也可以是字符串。在module里使用时,import会
把符号导入到module的上下文。
对于动态语言,保持全局域简洁是很重要的。 pl.strict模块,引入了简单的强制措施:全局变量必须
声明。如下:
> require 'pl.strict'
> print(x)
stdin:1: variable 'x' is not declared
> x = nil
> print(x)
nil strict模块符合Penlight的“用时加载”策略。
strict也禁止对全局变量赋值,除非在主程序里。如果你想绕过这个机制使用rawset和rawget。
如果你想让strict全局生效,在pl/init.lua最后加上require 'pl.strict',并在主程序里调用。
从1.1.0之后,strict提供了strict.module函数来创建(或修改)模块,以符合此机制。
如:
-- mymod.lua
local strict = require 'pl.strict'
local M = strict.module (...) function M.answer ()
return 42
end return M
如果你不小心错打成 mymod.Answer(),你会得到运行时错误:“variable ‘Answer’ is not declared
in ‘mymod’”。
这对已有的模块也有效,也可以修改lua标准库。
strict.make_all_strict(_G)
这样当你错打成
Penlight的函数参数是什么?math.cosine
时会得到错误信息,而不是nil。
许多Penlight里的函数的参数是函数,如map,它把函数作用到table的每个元素上。
pl.operator 导出了lua里的运算符,且和Python里的名字一样。如 operator.gt表示">".
map函数传递任何参数到函数,因此可以简写ls:filter(function(x) return x > 0 end)
为ls:filter(‘>’,0)
最后,pl.func支持Boost lambda(译注:C++里的)的占位符,如一个乘法的匿名函数可以
表示为;_1*_2.
可以直接用,在Penlight里的所有的函数参数都会通过 utils.function_arg ,pl.func在这个
函数中注册。所以你可以直接在标准函数里使用占位符。
> _1 = func._1
> = List{10,20,30}:map(_1+1)
{11,21,31}
(译注:_1,_2表示第几个参数)
另外一个有用的是utils.string_lambda ,也会自动调用
> = List{10,20,30}:map '|x| x + 1'
{11,21,31}
(译注:即|x|=x+1)
少用循环的利弊
循环是一种更机器的方式,如:
local res = {}
for i = 1,#ls do
res[i] = fun(ls[i])
end
但是这可以更高效的写作,ls:map(fun),不但代码更少,而且更易理解。
通常人们认为,这么写效率低,更消耗内存。如ls1:map2(‘*’,ls2):reduce ‘+’
会产生多余的临时对象。但是效率是相对的。
写循环容易出错而且乏味。(译注:省略了许多议论,结论是利大于弊) 通用函数
utils.memoize用来存储需要多次调用且花销大的函数。它可以保存调用函数后的已有结果,
如果参数相同直接返回结果。
sum = utils.memoize(function(n)
local sum = 0
for i = 1,n do sum = sum + i end
return sum
end)
...
s = sum(1e8) --takes time!
...
s = sum(1e8) --returned saved value!
应用支持
app.parse_args是一个简单的命令行参数解析器,支持gnu风格。
app.appfile,用来存储应用的配置。
prett模块让table更易读。(译注:省略了很多用法讲解,可以直接看api)
简单的面向对象
提供了面想对象的支持,
-- animal.lua class = require 'pl.class' class.Animal() function Animal:_init(name)
self.name = name
end function Animal:__tostring()
return self.name..': '..self:speak()
end class.Dog(Animal) function Dog:speak()
return 'bark'
end class.Cat(Animal) function Cat:_init(name,breed)
self:super(name) -- must init base!
self.breed = breed
end function Cat:speak()
return 'meow'
end class.Lion(Cat) function Lion:speak()
return 'roar'
end fido = Dog('Fido')
felix = Cat('Felix','Tabby')
leo = Lion('Leo','African') $ lua -i animal.lua
> = fido,felix,leo
Fido: bark Felix: meow Leo: roar
> = leo:is_a(Animal)
true
> = leo:is_a(Dog)
false
> = leo:is_a(Cat)
true 也可以这么写:
local class = require 'pl.class' class.Named {
_init = function(self,name)
self.name = name
end; __tostring = function(self)
return 'boo '..self.name
end;
} b = Named 'dog'
print(b)
--> boo dog Penlight提供了一些有用的类如:List,Set,Map,MultiMap,OrderdMap.
所有的类有catch函数,可以处理未预料的情况,如:
Strings:catch(function(self,name)
return function() error("no such method "..name,2) end
end)
上面只是简单的输出错误,但你可以创造一些好玩的,如;
Strings:catch(List.default_map_with(string)) ls = Strings{'one','two','three'}
asserteq(ls:upper(),{'ONE','TWO','THREE'})
asserteq(ls:sub(1,2),{'on','tw','th'})
(译注:list没有upper,sub,但是catch让没有的函数都执行string里函数)
cast类型转换,它不产生新对象,而是返回你传入的对象。
local sizes = ls:map '#' --即取长度
asserteq(sizes, {3,3,5})
asserteq(utils.type(sizes),'Strings')
asserteq(sizes:is_a(Strings),true)
sizes = Vector:cast(sizes)
asserteq(utils.type(sizes),'Vector')
asserteq(sizes+1,{4,4,6})
(译注,ls即Strings{'one','two','three'})
utils.type可以返回类中_name属性的字符串。
属性模式,我们想控制用户对属性的访问,但不想让用户,使用obj:get_field()这么长
的代码访问。解决办法如下:
local MyProps = class(class.properties)
local setted_a, got_b function MyProps:_init ()
self._a = 1
self._b = 2
end function MyProps:set_a (v)
setted_a = true
self._a = v
end function MyProps:get_b ()
got_b = true
return self._b
end local mp = MyProps() mp.a = 10 asserteq(mp.a,10)
asserteq(mp.b,2)
asserteq(setted_a and got_b, true)
规则是内部的变量以"_"开头,当访问mp.a时先检查特定的getter(取值器),
get_a。同理setter(设定器)也被使用了。
当然这也带来了更多的花销,所以你需要自己把握。
原文:http://stevedonovan.github.io/Penlight/api/topics/01-introduction.md.html