如何在Lua中实现延迟执行?

我一直在想是否有可能在 Lua 中实现像 .NET Linq 那样的延迟执行,只是出于好玩的目的。

在 .NET 中,我们可以创建一个称为“IEnumerable”的元素序列。这些元素可以通过各种方式进行过滤,例如映射/缩小(“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

然而,在编写此内容后,我意识到这不是真正的延迟执行..这只是使用绿色线程的赞美迭代器。

我正在尝试将其实现,以更好地了解函数式编程如何在我已经了解的语言中工作。

你有什么想法?

点赞
用户90511
用户90511

在 Lua 中获取延迟执行的方式是使用函数。你需要从以下 API 进行更改:

Where( x > 1 )

改为:

Where(function(x) return x > 1 end)

一个完整的工作示例类似于以下代码,为了保持简单,我省略了链式语法。

-- 一个流是一个函数,每次调用它都返回一个不同的值,
-- 在生成最后一个值后返回 nil。它有点像 ipairs 返回的值。

-- 输入列表,返回一个生成其值的流
function Each(xs)
  return coroutine.wrap(function()
    for _, x in ipairs(xs) do
      coroutine.yield(x)
    end
  end)
end

-- 输入一个流并返回一个由条件谓词过滤的新流
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

如果你想知道如何处理链式,并且以“流”类型表示对象而非纯函数,则可以采用以下方法实现:

local Stream = {}

-- 低级流构造函数接收生成器函数
-- 与 coroutine.wrap 返回的函数类似。您可以更改 API 以返回多个值(如 ipairs 所做的)。
function Stream:new(gen)
  local stream = { _next = gen}
  setmetatable(stream, self)
  self.__index = self
  return stream
end

-- 输入一个条件谓词并返回一个过滤流
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() )

协程更多的是让你以直观的方式编写协作函数,无需将其中一个“反转”。例如,实现列表迭代器时完全可以不使用协程:

-- 如果您尝试编写自己的 ipairs 函数,而不使用协程,
-- 它可能会像下面这样。
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

由于返回了“获取下一个”函数,因此我们能够一次仅获取一个元素。但是,我们必须将 for 循环“展开”,将其转换为 if 语句并手动更新循环计数器。我们还需要明确地跟踪所有迭代状态。在本例中,只有循环计数器,但在具有递归的协程中,您将需要维护堆栈,并且如果协程的体中有多个 yield,则需要一些状态标记来执行程序计数器的作用。

2014-07-01 23:01:41