[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Non Local Exits: Some usage notes
- From: Rici Lake <lua@...>
- Date: Fri, 12 Aug 2005 04:47:29 -0500
I've mentioned non-local exits as first-class objects a few times on
this list, and the reaction has always been somewhat guarded. I think
this might be because the concept is fairly unfamiliar to people.
I tried to find some Dylan tutorials on the issue but there is an
unfortunate lack of Dylan material on the web, although you can read
about the Dylan implementation in the online DRM (it's got quite a few
more features than the one I proposed.)
So I'll just scrawl a few possibly motivating examples. I personally
think that they are a brilliant idea, because they are a single
easily-implemented primitive which can be used to implement a wide
variety of useful control structures.
A common usage is simply to provide a cleanup clause for code execution:
function do_with_lock(fn, ...)
local a = lock()
block ()
return fn(...)
finally
a:release()
end
end
The nle is not even used in the above example, but it still manages to
guarantee that the lock is released (and without being awkward about
return values, which is a nice plus). This could just as well have been
written using the nle:
function do_with_lock(fn, ...)
local a = lock()
return block (p)
p(fn(...))
finally
a:release()
end
end
But the nle is mostly useful for being passed into called functions, as
I demonstrated with my first example a couple of messages ago.
nle's can also be used to implement an exception system. We're used to
exceptions being handled after the stack is rolled back, but this
implementation executes the exceptions on top of the stack, somewhat
like Lua's error functions. Error functions need to execute on the top
of the stack in order to be able to get a stack trace, but there are
other good reasons to do it, notably the fact that it gives us the
opportunity to handle an error and simply continue.
local function try(exception, func, arg)
local val, err = func(arg)
if val then
return val
else
return try(exception, func, exception(arg, err))
end
end
function handle_files(list)
local f
return block (p)
-- The exception handler is defined here.
local function nofile(filename, reason)
io.write("I couldn't open ",filename," because ",reason, "\n",
"What file should I try? ")
filename = io.read()
-- we invoke the nle if they don't have any useful suggestions
if filename == "" then p(nil, "Couldn't open "..filename) end
return filename
end
for _, filename in ipairs(list) do
f = try(nofile, io.open, filename)
... do something with f
f:close()
end
return true
finally
if f then f:close()
end
end
What we would probably do, rather than the somewhat manual approach. is
dynamically assign the nofile function (effectively, a restartable
exception handler) to the named exception thrown by io.open. (That is,
assuming we'd written the io library to use named exceptions.) Again,
finally clauses can be used to maintain the mapping between named
exceptions and exception handlers:
local old_nofile = Exceptions.nofile
...
return block (p)
Exceptions.nofile = function(filename, reason) -- as above -- end
...
finally
Exceptions.nofile = old_nofile
end
Clearly that can be simplified with a functional, too:
function Catching(exception, handler, func, ...)
local temp = Exceptions[exception]
Exceptions[exception] = handler
block ()
return func(...)
finally
Exceptions[exception] = temp
end
end
Catching("nofile", my_nofile, handle_filelist, files)
Dylan actually implements exceptions in precisely this fashion, but the
macro facility allows it to be written in a non-functional syntax,
using exception clauses in the block expression. (It's just translated
into the equivalent of the above code, though)