我一直想知道是否有可能在Lua中实现延迟执行,.NET Linq-style,仅用于测试。
在.NET中,我们可以创建一系列称为IEnumerable
的元素。然后可以通过各种方式过滤这些元素,例如map / reduce(Select(predicate)
,Where(predicate)
),但这些过滤器的计算仅在您枚举IEnumerable时执行 - 它是延迟的。 / p>
我一直试图在Lua中实现类似的功能,虽然我对Lua很生疏,并且还没有触及它一段时间。我想避免使用已经为我这样做的库,因为我希望能够在纯Lua中尽可能地使用它。
我的想法是,或许可以使用协同程序..
Enumerable = {
-- Create an iterator and utilize it to iterate
-- over the Enumerable. This should be called from
-- a "for" loop.
each = function(self)
local itr = Enumerable.iterator(self)
while coroutine.status(itr) ~= 'dead' do
return function()
success, yield = coroutine.resume(itr)
if success then
return yield
else
error(1, "error while enumerating")
end
end
end
end,
-- Return an iterator that can be used to iterate
-- over the elements in this collection.
iterator = function(self)
return coroutine.create(function()
for i = 1, #self do
coroutine.yield(self[i])
end
end)
end
}
tbl = {1, 2, 3}
for element in Enumerable.each(tbl) do
print(element)
end
table.insert(tbl, 4)
for element in Enumerable.each(tbl) do
print(element)
end
然而,在写完这篇文章后,我意识到这并不是真正推迟执行的......这只是使用绿色线程的美化迭代器。
我试图用我已经知道的语言来实现它以更好地理解函数式编程的工作原理。
思想?
答案 0 :(得分:4)
在Lua中获得延迟执行的方法是使用函数。您需要从
更改APIWhere( x > 1 )
到
Where(function(x) return x > 1 end)
完整的工作示例将与以下代码类似。我省略了链接语法以保持简单。
-- A stream is a function that returns a different value each time you call it
-- and returns nil after the last value is generated. Its a bit like what ipairs returns.
-- Receives a list, returns a stream that yields its values
function Each(xs)
return coroutine.wrap(function()
for _, x in ipairs(xs) do
coroutine.yield(x)
end
end)
end
-- Receives a stream and returns a new stream, filtered by a predicate
function Where(input, pred)
return coroutine.wrap(function()
for x in input do
if pred(x) then
coroutine.yield(x)
end
end
end)
end
local ys = {1,2,3,4,5}
for y in Where(Each(ys), function(x) return x <= 2 end) do
print(y)
end
如果您想知道如何处理链接,那么执行此操作的方法是将“stream”类型设置为使用方法而不是普通函数的对象。
local Stream = {}
-- The low level stream constructor receives a generator function
-- similar to the one coroutine.wrap would return. You could change the API
-- to something returning multiple values, like ipairs does.
function Stream:new(gen)
local stream = { _next = gen}
setmetatable(stream, self)
self.__index = self
return stream
end
-- Receives a predicate and returns a filtered Stream
function Stream:Where(pred)
return Stream:new(coroutine.wrap(function()
for x in self._next do
if pred(x) then
coroutine.yield(x)
end
end
end))
end
function Stream:Length()
local n = 0
for _ in self._next do
n = n + 1
end
return n
end
function Each(list)
return Stream:new(coroutine.wrap(function()
for _, x in ipairs(list) do
coroutine.yield(x)
end
end))
end
local ys = {10, 20, 30, 40}
print( Each(ys):Where(function(x) return x <= 20 end):Length() )
协同程序更多的是让你以直接的方式编写协作功能,而无需将其中一个“内外”。例如,完全可以在不使用协同程序的情况下为列表实现迭代器:
-- if you try to code ipairs on your own, without coroutines
-- it might look a bit like this
function Each(xs)
local i=1
return function()
if i <= # xs then
local x = xs[i]
i = i + 1
return x
else
return nil
end
end
end
由于我们返回“getnext”函数,因此我们一次只能获取一个元素。但是,我们必须“炸毁”for循环,将其转换为ifs并手动更新循环计数器。我们还需要明确地跟踪所有迭代状态。在这种情况下,它只是循环计数器,但在带有递归的协同程序中,你需要保持一个堆栈,如果协程在它的主体中有多个产量,那么你需要一些状态标志来完成程序计数器的工作。