[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Fun with function wrapping
- From: Rici Lake <lua@...>
- Date: Thu, 8 Feb 2007 09:24:54 -0500
On 8-Feb-07, at 3:06 AM, Bret Victor wrote:
So, that post about redefining "require" got me thinking about
function wrapping. It would be neat to have a function "wrap"
such that you could write the "require" redefinition as:
require = wrap(require, function (f)
local wasInsideRequire = isInsideRequire
isInsideRequire = true
f()
isInsideRequire = wasInsideRequire
end)
In general, you pass "wrap" a "wrappee" and a "wrapper":
wrappedFoo = wrap(foo, function (f)
stuffToDoBeforeFoo()
f() -- foo gets called here, somehow
stuffToDoAfterFoo()
end)
<snip>
Anyone else have an interesting take on this problem?
This situation with varargs shows up a lot; there is a section
about dealing with it on the Wiki in the
lua-users.org/wiki/LuaDesignPatterns page.
It's easier to accomplish with a slightly different interface:
wrap(func, before, after)
where before receives the arguments to the function, and after
receives an error indication and the results, like the return
from pcall (which is what wrap() is going to use) [Note 1]
This is essentially continuation-passing-style, as the Wiki
page notes; that is, calls are turned into tail calls by
splitting the logic between two (or more) functions:
function wrap(func, before, after)
local returnvalues(ok, ...)
after(ok, ...)
if ok then return ...
else error(..., 0)
end
end
return function(...)
before(...)
return returnvalues(pcall(func, ...))
end
end
However, that doesn't allow any communication between before
and after. We can fix that by requiring before()
to return the after() function, so now we have:
function wrap2(func, before)
local returnvalues(after, ok, ...)
after(ok, ...)
if ok then return ...
else error(..., 0)
end
end
return function(...)
return returnvalues(before(...), pcall(func, ...))
end
end
For example, to maintain a dynamically bound global variable,
as you suggest ([Note 2]), you could use this:
require =
wrap2(require, function() local save = InRequire
InRequire = true
return function()
InRequre = save
end
end)
This isn't a complete CPS transform, of course: if the
wrapped function was originally tailcalled, perhaps as
part of a state machine, the wrapped version will
quite possibly overflow the stack.
------------------------------------------------
Note 1: It's important to use pcall if you need to
maintain a global constraint around a wrapped function;
otherwise, if the function throws an error, the
constraint will not be restored.
Note 2: I still think that my solution to detecting
whether a module was required is better: it has the
significant advantages:
-- it works. That is, I actually use it in real work :)
-- it's robust
-- it does not require any special set-up, extra libraries,
etc.; it will work as advertised on a stock Lua
stand-alone interpreter
-- it's only one line, which can be inserted at the
top of your module if you're using module():
local IWasRequired = type(package.loaded[...]) == "userdata"
The disadvantage is that it depends on an undocumented
internal of the implementation of require(). In a future
Lua version, I may have to change it.