一个悄无声息失败的Lua迭代器?

我有一个非常简单的问题需要使用一个简单的迭代器。

假设我设计了一个名为“files()”的函数,可以迭代访问文件夹中的所有文件:

for file in files("/path/to/folder") do
  print(file)
end

现在,看起来完美无缺,但是有一个问题:如果文件夹不存在或者我们没有读取它的权限怎么样?

我们如何表明这样的错误?

一种解决方案是在这种情况下让“files()”返回“nil,无读取权限”。我们可以将“files()”调用包装在“assert()”中:

for file in assert(files("/path/to/folder")) do
  print(file)
end

这似乎解决了问题。但是,这强迫我们的用户总是使用“assert()”。如果用户不关心错误怎么办?对于这种用户,我们希望我们的“files()”表现得好像文件夹是空的。但是Lua--如果“files()”表明错误--会尝试调用返回的“nil”,这将导致错误(“尝试调用一个空值”)。

那么,

我们如何设计一个迭代器“files()”,以迎合既关心错误的用户,又不关心错误的用户?

如果不可能,你会建议什么替代方案?

点赞
用户2328287
用户2328287

有一个问题是,如果在迭代过程中出现错误(例如,对于递归迭代的子文件夹访问被拒绝),你想要做什么。在这种情况下,assert 无法帮助你。

在这种情况下,我创建了两个迭代器的变体(内部和外部)。

-- 抛出错误
for file in files(...) do ... end

-- 返回错误
files(...,function(file) ... end)

或者只创建两个不同的迭代器。

2014-06-11 08:45:34
用户3677376
用户3677376

首先:不要返回 nil 加错误消息,考虑在 files 函数中引发错误(使用 error)。这样你就不会忘记 assert 调用,并且不会出现令人困惑的 "attempt to call a nil value" 错误。

当你不想引发错误时,可以在 files 中传递一个额外的布尔参数——在这种情况下,你应该返回一个空函数 (function() end),而不是调用 error

一个更通用的方法如下所示:

-- 立即停止 for 循环的迭代器
local function dummy_iter() end

-- 捕获错误并在这种情况下跳过 for 循环
function iterpcall( g, ... )
  local ok, f, st, var = pcall( g, ... )
  if ok then
    return f, st, var
  else
    return dummy_iter
  end
end

for file in iterpcall( files, "/path/to/folder" ) do
  print( file )
  for line in iterpcall( io.lines, file ) do -- 也适用于其他迭代器
    print( line )
  end
end

上面的 iterpcall 实现仅处理迭代器 生成器 ( filesio.lines ) 中引发的错误,而不是迭代器函数 ( f ) 本身中引发的错误。你必须在一个带有 pcall 的闭包中包装 f 才能做到这一点。

2014-06-11 11:12:27