lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



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.