函数编程
序列
Lua 迭代器 (最简单的形式) 是一个函数,可以多次调用返回一个或多个值。for in语句理解迭代器和循环,直到该函数将返回nil。 Lua有标准的序列迭代器 (ipairs和pairs) , io.lines是返回文件中的所有行的迭代器。在的Penlight库中,这种迭代器也称为序列。单个值 (比如从io.lines ) 的序列称为单值,由pairs定义的序列是双值.
pl.seq提供一些有用的迭代器和一些操作序列的函数。乍一看本示例尝试在Lua写里 Python (有序列):
> for i in seq.range(1,4) do print(i) end
1
2
3
4
但是range 实际上相当于 Python 的xrange,因为它会生成一个序列,而不是列表。若要获取列表,请使用seq.copy(seq.range(1,10)),它使用任何单值序列作参数,并从seq.list生成表,seq.copy的行为和ipairs一样,除了它不给你索引中,只有值。
> for x in seq.list {1,2,3} do print(x) end
1
2
3
enum 可以把序列变成双值序列组,所以enum(list(ls))是实际上相当于ipairs 。一个更有趣的例子,打印出带有行号的文件:
for i,v in seq.enum(io.lines(fname)) do print(i..' '..v) end
序列可以合并,由 'zipping(压缩)' 它们或将它们连接起来。
> for x,y in seq.zip(l1,l2) do print(x,y) end
10 1
20 2
30 3
> for x in seq.splice(l1,l2) do print(x) end
10
20
30
1
2
3
seq.printall用于打印出单值的序列,并提供一些格式化的精细控制,如分隔符,断行,可以使用格式化字符串(详见string.format) 的。
> seq.printall(seq.random(10))
0.0012512588885159 0.56358531449324 0.19330423902097 ....
> seq.printall(seq.random(10), ',', 4, '%4.2f')
0.17,0.86,0.71,0.51
0.30,0.01,0.09,0.36
0.15,0.17,
map 可以把在序列上应用函数。
> seq.printall(seq.map(string.upper, {'one','two'}))
ONE TWO
> seq.printall(seq.map('+', {10,20,30}, 1))
11 21 31
filter 可以使用布尔值的函数 (通常称为谓词)来筛选。例如,此代码仅打印文件中的全是数字的行:
for l in seq.filter(io.lines(file), stringx.isdigit) do print(l) end
下面的示例返回所有大于0的表 (相当于tablex.filter(ls, ‘>’, 0))
ls = seq.copy(seq.filter(ls, '>', 0))
在讨论 input.numbers时已经提到过了seq.sum。这也可以用seq.reduce :
> seq.reduce(function(x,y) return x + y end, seq.list{1,2,3,4})
10
seq.reduce可以递归的调用二元函数(译注:即带有两个参数的函数),如:
reduce(op,{1,2,3}) => op(1,reduce(op,{2,3}) => op(1,op(2,3))
(译注:即不断的向op传入两个参数,直到最后两个)
现在可以轻松地进行累积操作,pl.operator中提供的标准操作很有用:
> ops = require 'pl.operator'
> -- can also say '*' instead of ops.mul
> = seq.reduce(ops.mul,input.numbers '1 2 3 4')
24
有一些函数可以从一个数字序列中提取统计数字:
> l1 = List {10,20,30}
> l2 = List {1,2,3}
> = seq.minmax(l1)
10 30
> = seq.sum(l1)
60 3
会经常碰到在值重复的序列里提取值。count_map可以对这种序列进行处理,返回每个键,及其关联的值是它们出现的次数所组成的表:
> t = seq.count_map {'one','fred','two','one','two','two'}
> = t
{one=2,fred=1,two=3}
它也可以处理数值序列,但你不能期望结果是适当的列表,即有没有“hole”。相反,您总是需要使用pairs来迭代结果 — — 请注意索引 5 处有一个hole:
> t = seq.count_map {1,2,4,2,2,3,4,2,6}
> for k,v in pairs(t) do print(k,v) end
1 1
2 4
3 1
4 2
6 1
unique使用count_map 返回唯一值得列表。
last 可以使用当前值和前一个值把单值序列转为双值序列:
> for current,last in seq.last {10,20,30,40} do print (current,last) end
20 10
30 20
40 30
这样就可以轻松做事,如识别文件中重复的行,或构建值之间的差异。filter 可以处理双值的序列,如通过使用operator.lt,或只是 ' <',返回当前值小于上一个值的序列,结果会复制到表中。
> ls = {10,9,10,3}
> = seq.copy(seq.filter(seq.last(s),'<'))
{9,3}
序列的包装
pl.seq 中的函数涵盖了处理序列时常见的模式,但将这些函数链接在一起可以导致丑陋的代码。考虑前面的例子 ,seq重复了三次,所生成的表达式要从右到左读。第一个问题可以使用局部变量别名解决,这样表达式改为copy(filter(last(s),‘<’)) 。第二个问题是指秩序有点不自然的应用程序。我们往往更喜欢从左到右进行读操作,这是为什么面向对象的表示法成为最受欢迎的原因之一。序列适配器允许此表达式就像这样:
seq(s):last():filter('<'):copy()
这种表示法,可以用到左到右的链式调用。
序列不是一个基本的 Lua 类型,他们通常是函数或可调用对象。表达式seq(s)将一个序列包装在sequence wrapper,它是一个对象,可以理解pl.seq中所有的函数。然后,此对象显式表示序列。
作为一种特殊情况,该构造函数 (这是当您调用表seq ) 将包装简单表。在这里我们将长度运算符应用到序列中的字符串,并将它们打印出来。
> seq{'one','tw','t'} :map '#' :printall()
3 2 1
为方便,seq.lines表现就像io.lines一样,除了它将包装的结果作为显式序列类型。这个例子需要从标准输入一次读入10 行,把读入的字符变为大写,并把它变成一个计数、值序列,再用连接运算符连接,最后打印出带有换行符结果。
seq.lines():take(10):upper():enum():map('..'):printall ' '
请注意方法upper,而不是一个seq函数。如果未知的方法被调用,序列的包装会将该方法应用到 (这是隐式使用的mapmethod )序列中的所有值 。
用这种方式创建自定义的序列很简单。在 Unix 上, /dev/random给你无限序列的随机字节,所以我们使用take 来限制序列,然后用map 缩放结果到所需范围。关键的一步是使用seq来包装的迭代器函数:
-- random.lua
local seq = require 'pl.seq'
function dev_random()
local f = io.open('/dev/random')
local byte = string.byte
return seq(function()
-- read two bytes into a string and convert into a 16-bit number
local s = f:read(2)
return byte(s,1) + 256*byte(s,2)
end)
end
-- print 10 random numbers from 0 to 1 !
dev_random():take(10):map('%',100):map('/',100):printall ','
另一个一行Linux 程序取数据于/proc文件系统,可以当前正在运行的进程的列表:
pids = seq(lfs.dir '/proc'):filter(stringx.isdigit):map(tonumber):copy()
此版本的Penlight有一个实验性的功能,它依赖于所有Lua 类型可以都有 metatables,包括函数的事实。这使隐式序列包装成为可能:
> seq.import()
> seq.random(5):printall(',',5,'%4.1f')
0.0, 0.1, 0.4, 0.1, 0.2
这可以避免如seq(seq.random(5))尴尬的构造,完全来自其他地方的迭代器,如下:
> ('one two three'):gfind('%a+'):printall(',')
one,two,three,
seq.import后不再需要显式包装序列函数。
但对于这种便利付出的代价。每个函数都受影响,这样的任何函数可以用,适当的或不适当的:
> math.sin:printall()
..seq.lua:287: bad argument #1 to '(for generator)' (number expected, got nil)
> a = tostring
> = a:find(' ')
function: 0042C920
什么函数被返回了?它是几乎肯定会在当前上下文中没有任何意义的东西。所以隐式序列可能会使某些类型的编程错误的更难抓到 — — 他们是最好用于交互式探索和小脚本。