序列适配器

lua-users home
wiki

对序列操作的更好表示法的需求

序列是由迭代器函数生成的连续值流。在最简单的形式中,它们由一个函数(或可调用对象)表示,该函数在每次调用时返回多个值,其中 `nil` 表示结束。序列可以是单值的(如 `io.lines`)或多值的(如 `pairs`),并且它们大多生成相同类型的值。提取我们在序列和表中使用的某些常见操作并将其作为库提供非常有用。这是 PenlightLibraries 的主要目标之一。

(请注意,这是一个特异性的定义:Lua 手册将序列定义为“一个表,其中所有正数键的集合等于 {1..n},其中 n 是一个整数” [1]。)

使用 pl.seq 以及 pl.func 提供的占位符表达式,我们可以说

seq.printall(seq.filter(seq.list{1,2,3,4},Gt(_1,2)))

这足够公平,但很笨拙;我个人会发现很难用它来激励这个,而不是一个简单的循环。当然,这是一个玩具示例,但更复杂的组合对于人类来说更难解析。序列适配器使我们能够将相同的操作表示为方法链

S{1,2,3,4}:filter(Gt(_1,2)):printall()

这更具可读性,原因有几个。第一个是限定符 `seq.` 不在任何地方,第二个是我们习惯于从左到右阅读操作序列,这与从右到左的函数应用不同。(这也是 Unix 使其流行的著名管道隐喻。)以下是一个示例,其中长度运算符映射到字符串序列上

S{'one','tw','t'} :map '#' :printall()  --> output: 3 2 1

使用真实的序列更有趣,例如由 `io.lines` 生成的序列。这将创建一个包含文件中所有唯一行的序列,然后将其复制到一个表中。

ls = S(io.lines(fname)):unique():copy()

另一个非常方便的模式是对序列的所有元素应用方法

S{'[one]','[two]','[three]'}:sub(2,-2):upper():printall() --> output: ONE TWO THREE

以下是一个更复杂的示例;给定一个 Lua 代码字符串,查找所有变量名。`lexer.lua` 将创建一个双值序列,第一个值为标记类型,第二个值为标记值。第一个值用于过滤标记流,以便仅传递变量 ('iden');`map(_2)` 仅让值通过,`unique` 收集名称,最后 `copy` 将这些名称转换为表。

str = 'for i=1,10 do for j = 1,10 do print(i,j) end end'
ls = S(lexer.lua(str)):filter(Eq(_1,'iden')):map(_2):unique():copy()
print(List(ls))
---> output: {i,print,j}

源代码在 [这里]

实现

由于没有唯一标识序列的类型(除了它是可调用的),因此我们需要一个包装器对象。迭代器函数被放入一个表中,并附加一个元表,以便我们可以控制方法查找。

当调用未知方法时,会调用__index元方法,然后在seq表中查找该方法。但是,此函数不能直接使用,因为 (a) 它期望第一个参数为序列,以及 (b) 它必须将结果返回到序列包装器中,以便我们可以继续方法链。

SMT = {
	__index = function (tbl,key)
		local s = seq[key]
		if s then
			return function(sw,...) return S(s(sw.iter,...)) end
		end
	end,
}

function S (iter)
	return setmetatable({iter=iter},SMT)
end

这演示了这个概念,但在实践中并没有处理所有必要的情况。一些seq函数返回普通值(如reduce)或表(如copy),包装这些值会令人困惑。像map这样的函数的实参顺序颠倒了(先函数,后序列)。如果S可以将表包装为序列,但对copy的结果不这样做,将会很方便。等等。还有一个问题是如何对序列值进行方法调用。关键在于seq中的这个函数

function mapmethod (iter,name,arg1,arg2)
	local val = iter()
	local fn = val and val[name]
	return function()
		if not val then return end
		local res = fn(val,arg1,arg2)
		val = iter()
		return res
	end
end

与普通的map不同,mapmethod被赋予一个函数名称,它在序列生成的第一个值中查找该名称 - 之后返回一个序列,该序列是将该函数应用于每个值的結果。(为了方便起见,前两个额外的实参被明确地捕获用于函数调用)。因此,当__index元方法无法查找函数名称时,其策略是使用该方法的名称调用mapmethod

-- seqa.lua
local seq = require 'pl.seq'

-- can't look these directly up in seq because of the wrong argument order...
local overrides = {
	map = function(self,fun)
		return seq.map(fun,self)
	end,
	reduce = function(self,fun)
		return seq.reduce(fun,self)
	end
}

SMT = {
	__index = function (tbl,key)
		local s = overrides[key] or seq[key]
		if s then
			return function(sw,...) return SW(s(sw.iter,...)) end
		else
			return function(sw,...) return SW(seq.mapmethod(sw.iter,key,...)) end
		end
	end,
	__call = function (sw)
		return sw.iter()
	end,
}

function callable (v)
    return type(v) == 'function' or getmetatable(v) and getmetatable(v).__call
end

function S (iter)
	if not callable(iter) then
		if type(iter) == 'table' then iter = seq.list(iter)
		else return iter
		end
	end
	return setmetatable({iter=iter},SMT)
end

function SW (iter)
	if callable(iter) then
		return setmetatable({iter=iter},SMT)
	else
		return iter
	end
end

SteveDonovan


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2012 年 7 月 4 日上午 10:25 GMT (差异)