On Mon, Jul 15, 2019 at 11:55 AM nobody wrote:
But these two things in combination create a problem:
> The statement works for all names independently of what a name is:
> local/upvalue/global.
> The white-list must contain only visible names, otherwise
> compilation error is raised.
What about to-be-defined "globals"? (Will you have to "pre-declare" them?)
I'm aware of two ways to turn on strict control over globals usage at compile-time:
1) separate the globals' namespace from the other namespaces (for example, by using prefix "$" or "_ENV.")
or
2) explicitly declare (white-list) globals you want to use in your code
This suggestion implements both these approaches:
1) You can separate namespaces (by using prefix "_ENV.")
with _ENV do
_ENV.print(_ENV.math.pi)
_ENV.non_existing_global = 42
end
2) You can white-list all the globals you need
with print, math, non_existing_global do
print(math.pi)
non_existing_global = 42
end
3) You can combine these two approaches:
most frequently used globals are white-listed, others are prefixed
with print, _ENV do
print(_ENV.math.pi)
_ENV.non_existing_global = 42
end
It's your own decision: to pre-declare to-be-defined globals or to use "_ENV." prefix for them.
And what about this?
Point2D.length =
function( _ENV ) with x, y do return (x^2+y^2)^0.5 end end
(which could (more or less) equivalently be written as
function Point2D:length( ) return (self.x^2+self.y^2)^0.5 end
but is shorter / more readable this way and already (without with)
gives the guarantee that you'd at most pollute the object you're
working on, not the outer environment.)
Nice example!
Yes, it would be more readable in "Point2D:length()" to use "self" instead of "with".
This example shows that for some (but not all) situations Lua already has a trick to write a "pure function".
But "with" statement gives more universal control over visibility of variables, and it also protects against typos.
If the price of an error is quite high and the inner block is much longer than "(x^2+y^2)^0.5" then you would probably prefer to use "with".
do
local print = print
local _ENV = { x = 23 }
function _G.foo( ) with print, x do print( x ) end end
-- up to this point you cannot even tell that _ENV might change
-- again (so this would break Lua's single-pass compilation)
function _G.bar( newenv ) _ENV = newenv end
-- only now it's clear that _ENV might be changed
end
bar { y = 5 }
foo ()
Because _ENV can change dynamically, you cannot determine at compile
time what names will be valid, so you cannot always raise a compile time
error.
What does "valid" mean?
Only visibility does matter.
The "with" statement doesn't care about whether a variable contains nil or non-nil value.
Absolutely all globals (including non-existing) are considered visible outside of the most outer "with" statement.
> Variant #1
>
> local function pure_func(x, y)
> with x, y do
> -- all upvalues and globals are inaccessible here
> ...
> end
> end
you have to write all arguments twice…
Yes, that's significant inconvenience.
See "suggestion #2" below to find how it could be fixed.
I'd prefer that to work like
local x, y, z with a, b, c do
… (both x,y,z AND a,b,c are visible here) …
end
OK, I see your point: it would avoid typing variable names twice as in the following code:
local x, y, z
with a, b, c, x, y, z do
-- both x,y,z AND a,b,c are visible here
end
OK, let's try to avoid this unneeded typing.
-----------------------------------------------------
Suggestion #2, improved:-----------------------------------------------------
Let "with" be not a statement, but an optional prefix for arbitrary Lua statement.
with white-list-names inner-statement
If inner statement is "do...end" then we are getting our previous form
with x, y
do
...
end
If inner statement is a local variables definition then we are getting exactly what "nobody" wants (don't typing local variables twice):
with x, y
local sum, diff = x+y, x-y
-- variables "sum" and "diff" are accessible outside "with"
print(sum, diff)
If inner statement is a local function definition then we are getting compact code to define a "pure function" (don't typing function parameters twice):
with math
local function pure_func(x)
return math.sin(x)/x
end
-- local variable "pure_func" is accessible outside "with"
print(pure_func(1))
Inner statement could be just anything:
with x
return x, x+1, x+2
with a, i, j
i, j, a[i], a[j] = a[i]~a[j], j+1&255, a[j], a[i]
Linebreaks in these examples are just for readability, spaces could be used instead.
It's impossible to use empty white-list because of the following ambiguity:
with a (b or c)[1] = 42
Is it
with
a(b or c)[1] = 42
or
with a
(b or c)[1] = 42
An underscore (or could be used instead of empty white-list (but only for most outer "with").
with _
do
....
end
with _
local function f(n)
return n*(n+1)/2
end
with _
local t = {
pure_func1 = function(x) return (x+1)/(x-1) end,
pure_func2 = function(x, y) return x*y+x+y end,
}
Please note the difference between defining a new local and defining a new global:
with x
local new_var = x*x -- new local
with x, new_var
new_var = x*x -- new global
This is because the local variable is actually being created by the statement,
but globals are considered already created (just like already existing local variable).
local old_var
with x, old_var
old_var = x*x -- old local