Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A Lua iterator that fails silently?

I have a very simple problem with a simple iterator.

Let's say that I design a function, files(), that iterates over all the files in a folder:

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

Now, this seems perfect, but there's a problem here: What if the the folder doesn't exist, or we don't have read permission to it?

How would we indicate such error?

One solution would be to have files() return nil, "no read permission" in this case. We'd then be able to wrap the call to files() inside assert():

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

This seemingly solves the problem. But this forces our users to always use assert(). What if the user doesn't care about errors? For this kind of users we'd want our files() to behave as if the folder is empty. But Lua --in case files() indicates error-- would try to call the returned nil and this will result in an error ("attempt to call a nil value").

So,

How can we design an iterator, files(), that would cater to both users that care about errors and users that don't?

If it's not possible, what alternative would you suggest?

like image 745
Niccolo M. Avatar asked Dec 25 '22 08:12

Niccolo M.


1 Answers

First: Instead of returning nil + error message consider raising an error in the files function (using error). This way you can't forget the assert call, and you won't get the confusing "attempt to call a nil value" error.

You could pass an extra boolean parameter to files when you don't want to raise errors -- you should return an empty function (function() end) instead of calling error in this case.

A more general approach is the following:

-- an iterator that immediately stops a for loop
local function dummy_iter() end

-- catch errors and skip for loop in that case
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 -- works for other iterators as well
    print( line )
  end
end

The implementation of iterpcall above only handles errors raised in the iterator generator (files or io.lines), not in the iterator function (f) itself. You would have to wrap f in a closure with a pcall to do that.

like image 66
siffiejoe Avatar answered Jan 13 '23 10:01

siffiejoe