Dan*_*try 4 lua lazy-evaluation deferred-execution
我一直想知道是否可以在 Lua 中实现延迟执行(.NET Linq 风格),只是为了好玩。
在 .NET 中,我们可以创建一系列元素,称为IEnumerable. 然后可以通过各种方式过滤这些元素,例如 map/reduce ( Select(predicate), Where(predicate)),但这些过滤器的计算仅在您枚举 IEnumerable 时执行 - 它被推迟。
我一直在尝试在 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
Run Code Online (Sandbox Code Playgroud)
然而,写完这篇文章后,我意识到这并不是真正的延迟执行。这只是使用绿色线程的美化迭代器。
我正在尝试使用我已经了解的语言来实现它,以便更好地理解函数式编程的工作原理。
想法?
Lua 中实现延迟执行的方法是使用函数。您需要将您的 API 更改为
Where( x > 1 )
Run Code Online (Sandbox Code Playgroud)
到
Where(function(x) return x > 1 end)
Run Code Online (Sandbox Code Playgroud)
一个完整的工作示例将类似于以下代码。为了简单起见,我省略了链接语法。
-- 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
Run Code Online (Sandbox Code Playgroud)
如果您想知道如何处理链接,方法是让“流”类型成为带有方法的对象,而不是普通函数。
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() )
Run Code Online (Sandbox Code Playgroud)
协程更多的是让您以一种简单的方式编写协作函数,而无需将其中一个函数“翻来覆去”。例如,完全可以在不使用协程的情况下实现列表的迭代器:
-- 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
Run Code Online (Sandbox Code Playgroud)
由于我们返回一个“getnext”函数,因此我们一次只能获取一个元素。然而,我们必须“炸毁”for 循环,将其转变为 ifs 并手动更新循环计数器。我们还需要显式地跟踪所有迭代状态。在这种情况下,它只是循环计数器,但在具有递归的协程中,您需要保留一个堆栈,并且如果协程在其主体中有多个收益,那么您需要一些状态标志来完成程序计数器的工作。