lua-users home
lua-l archive

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


On Thu, Mar 8, 2012 at 8:20 PM, Peter Cawley <lua@corsix.org> wrote:
> [...] I'm not sure which of
> them is the most intuitive or most useful:
> 1) "const E" is syntactic sugar for "const_E" where "local const_E =
> E" is inserted at the earliest point in the source code such that the
> bindings of the identifiers in E are unchanged, and the name "const_E"
> is for exposition only.
> 3) "const E" causes a new entry to be made in the prototype's constant
> table, the value of which is computed the first time "const E" is
> evaluated, and then re-used from thereafter. The behavioural semantics
> of this are different to 1) and 2), but pressure on locals and
> upvalues is abolished.

ignoring the concern about the "too many local variables" error for
now, I'll comment instead on whether the lazy evaluation of #3 is
preferred over the eagerness of #1.

In cases like your example, one pattern I sometimes follow as a
compromise is to declare a variable outside a function scope but
initialize it inside the function on first call:

  local _ws
  function f(...)
   -- ...
   _ws = _ws or set {"ListBox", "Label", ...}
   for widget_type in ws do
     -- ...
   end
   -- ...
  end

This preserves most of the efficiency and textual locality.  It also
happens to be in the "lazy" direction, though that's a side-effect I
typically don't care about.

in other times I see a use for the more eager direction.  For example,
in some languages (e.g. Perl, Java, and OCaml), you don't need to
explicitly import a module to use its functions if the names of those
functions are fully qualified.  This can likewise be done in various
forms in Lua:

  _ENV = setmetatable({}, {__index = function(t, name) return
require(name) end})
  local function f()
    return string.gsub(lfs.currentdir(), '\\', '/')
  end

A complaint about this form, which after all is not often done, is
that any errors in the module loading are delayed until late in the
run-time execution.   Sometimes you want that laziness (e.g. optional
dependencies), but more often probably not.  If our test suite never
calls f(), we may miss detecting if "lfs" has a problem.  (True, we
also fail to eagerly detect if 'currentdir' is misspelled unless we
move a `local lfs_currentdir = assert(lfs.currentdir)` outside the
function, and more on that later.)   Even if the code is correct, f is
now more prone to failure at inopportune times--e.g. disk I/O failure
on loading a module when f is called in the middle of a transaction
rather than during module loading.  However, "const" can help here if
it forces a more eager evaluation:

  local function f()
    return (const string.gsub)((const lfs.currentdir)(), '\\', '/')
  end
  ----> translates to
  -- local _a = assert(string.gsub)
  -- local _b = assert(lfs.currentdir)
  -- local function f() return _a(_b(), '\\', '/') end
  ---- Note: above assert's are implied on assuming 'attempt to call a
nil value' is not raised.

The result is not just greater efficiency but also more eager
detection of run-time errors.