lua-users home
lua-l archive

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


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