lua-users home
lua-l archive

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




Le lun. 21 juin 2021 à 19:31, Egor Skriptunoff <egor.skriptunoff@gmail.com> a écrit :

Interesting set of rules!
Let's see whether each sugar existing currently in Lua complies with these rules.
("LR" below is the abbreviation for "Lorenzo's Rule")

function f() end    -- sugar
f = function() end  -- meaning
LR1: yes
LR2: yes
LR3: no   -- syntax is not applicable when defining table fields
LR4: no   -- fundamental concept is obscured: function is a value

OK but  the function value also has a name by itself (independantly of the name of the "local variable", actually a string key in some table which is in the current environment stack in scope (i.e. in some closure).

local function f() end      -- sugar
local f; function f() end   -- meaning
local f = function f() end  -- incorrect meaning
 
 I don't see why it would be "incorrect meaning" as this contradicts the previous case: if it means "local f; functionf() end", then the second statement  means "f = function() end", so this becomes "local f; f = function() end", two statement: the first one defining the local "variable", i.e. creating or overwriting the key "f" in the table at top of the current environment (or the innermost closure), with an initial nil value, which is then immediately replaced by the non-nil assigned function.

Note that defining a local variable is a bit more complex than just assigning a key in a table: when you declare it without a value, its initial value nil does not delete any value from any table in scope, it actually assigns a numbered slot in the topmost closure; that slot has no name at runtime (but may have info tracking its source name only for debugging, just like this debug info could contain the name or path of the Lua script, a reference to its loader or compiler and other fields used by the execution machine or VM implementing the language, including exception/error handlers, pointers or handles to some inner resources or table of resources for the underlying machine or OS, or to security and restriction descriptors required by some additional policy manager, or any field that would be useful for reflection/introspection in a debugging/tracing API offered by the Lua VM itself or its internal compiler, and not necessarily by the Lua script itself unless that API is also made accessible in Lua).

But then outside of the closure scope, that nil field is not visible; it will become visible if that function is added to the exported set of names exposed to other closures within the environment stack (which itself may also have mechanisms to control the access to elements in the stack, including security restrictions which may hide or obscure these names or may reject unauthorized accesses without providing some secure authorization token.

For this reason I think that this second example:
   local function f() end -- sugar
should really have this actual meaning (with a single statement combining the creation and initialization of a slot in the innermost closure:
   local f = function() end
and probably not (separate creation and initialization with nil, followed by an assignment. The difference may be visible if you add other restrictions such as <const> where you cannot reassign that variable which can only be set at initialization time):
    local f; f = function() end
but it could as well mean:
    local f = function f() end
where the extra explicit label is the name that would be exposed to reflection, so that the function value itself is no longer anonymous (independantly of the name of the local variable defined in the topmost closure, which is discarded by the compiler at runtime, and used and replaced positionally by a numeric index only, not in a Lua "table" but a statically-sized array, not using any hashing like with regular and "resizable" dynamic Lua "table" objects).

The closure's array is not a Lua object, it is not modifiable at run time by Lua script, it is genrated by the Lua compiler, allocated by the Lua VM, pushed/popped on the environment stack (which also contains a statically indexed field for the regular environment table containing publicly exposed names, whose assigned value could point (but not always) to the same objects as those pointed by the closure's static array indexed by integers. Access to the closure is much faster and efficient than access to tables: no complex lookup needed, direct access by the VM, and the possibility for the VM to cache closure positions in that array using registers: the array is just a convenient store which may be not constantly in sync with the registers where the content was cached, and in some cases, it is possible for the VM to even eliminate some closure positions (or reuse them) when it does not need such store, to minimize the computed size of the closure's array. As well assigned values in the closure may be native types and not necessarily full Lua objects or values, which would be allocated/created on the fly when needed, i.e. if that value has other references from variables or table fields exposed externally.

Closures in Lua is the most powerful storage feature: it is fast, efficient, secure, allows lot of optimizations in the Lua VM. It is then followed by tables that expose the public API and what Lua scripts are allowed to alter dynamically (but this has a performance price, notably access time for hashing, access restriction checks in the VM, depending on its policy manager, all of this beging controled by getters/setters in Lua objects which are also programmable in Lua using standardized fields in "metatables").