在Lua中实现延迟执行?

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)

然而,写完这篇文章后,我意识到这并不是真正的延迟执行。这只是使用绿色线程的美化迭代器。

我正在尝试使用我已经了解的语言来实现它,以便更好地理解函数式编程的工作原理。

想法?

hug*_*omg 6

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 并手动更新循环计数器。我们还需要显式地跟踪所有迭代状态。在这种情况下,它只是循环计数器,但在具有递归的协程中,您需要保留一个堆栈,并且如果协程在其主体中有多个收益,那么您需要一些状态标志来完成程序计数器的工作。