【问题标题】:Implementing deferred execution in Lua?在 Lua 中实现延迟执行?
【发布时间】:2014-07-01 19:45:19
【问题描述】:

我一直想知道是否可以在 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 

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

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

想法?

【问题讨论】:

  • 哎呀,错误参数是错误的方法。忽略这一点,:)
  • 您的问题是什么? Lua 不是函数式语言。
  • "想知道是否可以实现延迟执行,..在Lua中..."
  • 另外,@Lua 不是函数式语言,section 6 of PIL 不同意你的观点。它不是像 Haskell 那样的纯函数式语言,但它具有函数式元素(即是一流的成员),我想知道是否有可能在 Lua 中实现延迟执行的概念,就像我在问题中所说的那样。当我的问题背后的前提在第一行时,我真的不明白“不清楚我在问什么”。

标签: lua lazy-evaluation deferred-execution


【解决方案1】:

在 Lua 中获得延迟执行的方法是使用函数。您需要从

更改您的 API
Where( 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

如果您想知道如何处理链接,方法是让“流”类型成为具有方法的对象而不是普通函数。

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

【讨论】:

  • 所以,等等,如果func 返回一个协程,我可以做for x in func 吗? (参考return coroutine.wrap,我没用过这个功能)。如果是这种情况,我该如何处理功能的延续?在这种情况下,这是主要关注点 - 能够执行以下操作:source:where(function(x) return true end):select(function(x) return x * 2):accumulate(function(x, y) return x + y end)
  • 查看for statement 的文档。 func 是一个函数,协程并没有什么神奇之处——我使用pairs 和ipairs 使用的相同接口。至于coroutine.wrap,它是一个帮助器,它返回一个包装coroutine.resume 的函数,而不是直接引用协程。
  • 关于获取链式语法,。它只是使用 OO 接口的问题,其中 WhereSelect 是流对象的方法,而不是使用普通的旧函数。有不止一种方法可以做到这一点(搜索一下 Lua 面向对象),但我添加了我的答案以包含一个示例。
  • 最后,我想强调的是,延迟代码的重要之处在于函数,这里没有必要使用协程或for x in blah 技巧。如果你愿意,你可以使用普通的对象和函数来实现一切,正如我在最后展示的 Each 函数所示。
  • > 并且没有基本需要在这里使用协程或 for x 等等技巧 是的,我可以理解这一点,我试图模拟 IEnumerable 接口,该接口仅在枚举时才实际执行,因此有兴趣推迟到枚举:P。我认为也不需要协程,它只是感觉最合适
猜你喜欢
  • 2011-05-18
  • 1970-01-01
  • 1970-01-01
  • 2011-03-04
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多