lua-users home
lua-l archive

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


Also note that if Lua was defined so that all variables are local by default, your function would need to be written something like:

  function (c,s)
      using __mul, __add, __sub
      return {x=c*x-s*y,y=s*x+c*y}
  end

i.e. you would need to explicitly import locally the external operators you need inside the code. You don't need to specify the variables c and s that are already explicitly imported locally in the environment (and bound with a value from the caller's environment).

Writing Lua code would be a severe nightmare: we would have to explicit imports (with a "using" declaration, behaving like "local" except that it initialized the local variable from the outer environment, using its "__index" method if needed) in every function we write, except if the function does nothing or just returns one of the variables in parameters or created with "local" (that function could not even perform any test on the value of parameters without the "__eq" or "__lt" or "__le" being explicitly imported by a "using" clause, and would not even be able to make a functional call to a function object given in parameter without importing the "__call" method with a "using" clause).

Lua is not C or C++: everything is an object, including functions themselves or constants (like integers and strings). Unlike _javascript_, Lua also makes all operators as true objects (bound to functions).

The only thing that you have in Lua to control and force the locality of named variables (and implicit variables for operators, which are a syntaxic sugar to perform function calls) is the "local" keyword. Everything else allows "inheritance" using the chain of environments.



Le mar. 13 nov. 2018 à 22:24, Philippe Verdy <verdy_p@wanadoo.fr> a écrit :
I don't think so; within the same block of statements, all variables are automatically bound to the same environment (i.e. a table), and the compiler does not need to know if it's local or external: all of them are local and accessed by the "__index" meta entry of the environment table, which is always used as  first level of indirection before performing an actual lookup to the environment table itself (not its metatable).
Unlike tables in Lua, all environments must have a metatable associated to their table, so there's always an "__index" entry in it (it also has a "__newindex" for assignments). A compiler may want to perform some optimizations for not creating a metatable with "__index" and "__newindex", but it cannot safely know if these two entries are set or not (they may be set by the block of instruction by using the fsetenv function, possibly by calling external functions which will execute with the parent element in their on environment linked to the parent environment, and so can also modify the parent environment).
So all names are local. The fact that when assigning a variable or reading it has an external effect comes only from the fact that the default "__index" function will lookup in parent environments in a chain to see if there's a matching name: if no such name is found in the chain, then the effect of reading the variable will return "nil"; the same occurs for "__newindex" which also tries to lookup the local table, then if not found performs a lookup in the parent environment, and if not found it will then create a new variable in the initial environment.
All you want is to stop the recursive lookup of variable names in the chain of environment, so that all variables behave as pure local variables (creating as many new variables as needed).
It's not really possible to block the recursion: your code even needs the chain for all basic operations (including operators like "+"). If you break the lookup, then your local code can simply do nothing at all!
Remember that the environment does not include only local variables, it also includes all functions and operators your code can use.
So your proppsed "blind" keyword in:
  function (_ENV,c,s) blind
      return {x=c*x-s*y,y=s*x+c*y}
    end
would have the effect of leaving only three names accessibles: _ENV, c and s, but operations like "=" (assignment made via "__newindex" function call), "*", "-", and "+" would also have no defined function (their lookup would return nil, and you'd then get errors: cannot call a function referenced by nil !

The only way to do that is to allow passing selected properties you need for your function to run, by creating a restrictive environment, in which the function:
    function (c,s)
      return {x=c*x-s*y,y=s*x+c*y}
    end
now can run in perfect isolation: it is effectively the case that variable names "x" and "y" are not defined locally, but you have to force them to use the local environment and not any parent environment, but you sill need the function references for the 3 arithmetic operators. Note that for function calls (including operator evaluations) there's also a "__call" entry in the environment to find matching function names: functions are not called directly.

An interesting reading:

or more generally

and the manual of course (which details all "__" prefixed functions needed in valid environment and that allow your code to be really executable) :




Le mar. 13 nov. 2018 à 14:23, Dirk Laurie <dirk.laurie@gmail.com> a écrit :
Op Di., 13 Nov. 2018 om 14:04 het Philippe Verdy <verdy_p@wanadoo.fr> geskryf:
>>
>> I'm not too sure how one could implement hiding of upvalues at the
>> language level. (At the implementation level, it's obvious. Just skip
>> the phase that looks for them.)
>
> This is not so obvious because Lua highly depends on this; the "phrase" that looks for it is exactly the one that lookups variables in the environment using its "__index" meta-entry, which is where the environment is already stated: so the first level of lookup would be required (otherwise the function itself not would have itself access its own local variables) but you want to avoid the recursion of the lookup to the next level to look for upvalues.
> Note that this recursion is a trailing recursion (so Lua optimizes it natively as a loop: the "phrase" you want to hide would be a statement within that loop, and you want it to be used only on a specific loop number to break that loop by returning early a "nil" value so that an "undefined variable error" can be stated). The difficulty is that there's no loop number which is accessible. So all I see you can do is to set the "__index" meta entry specifically to your need.

I think we are talking at cross-purposes.

Whether a name is recognized as an upvalue happens at compile time. No
metatable is involved. It's a question of what is in scope.

Do 'luac -l' for my two examples. The one without "x=1" generates the
instruction   GETTABLE     4 0 -1    ; "x"
but the one with "x=1" generates   GETUPVAL     4 0    ; x

The scope of a name is lexical. That means there is a sequence of
local scopes with the entire chunk outermost, each containing a
smaller scope until we get to the innermost scope. The compiler does
this when one refers to 'x':

1. Is there a local variable named 'x' in the innermost scope? If so,
it does not need to be loaded: the VM instruction can access it
directly.
2. For each containing containing scope working outwards, the question
is asked again. If a local variable named 'x' is found in that scope,
a GETUPVAL instruction is generated to load the variable via the
upvalue list that sits in the function's closure.
3. If no containing scope has 'x', a GETTABLE instruction is issued to
load the value as a table access from _ENV.

The requested "blind" keyword would merely tell the compiler to treat
the current innermost scope, from that point onwards, as not having a
containing scope, so that step 2 is an empty loop.

Youmay have been thinking of what happens in case 3: the GETTABLE from
_ENV could trigger a whole chin of __index metamethods, depending on
what you have done with _ENV (in fact, since this idiom is used in an
object-oriented paradigm, your _ENV is an object which may well have a
complicated metatable).