Autolocals?

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

Autolocals?

Alexander Gladysh
Hi, list!

I want to bring my own pet feature to discussion.

First, some background.

I do not write my own native Lua modules in my project, but organize
each file as follows:

    local foo, bar, baz = import 'path/to/file.lua' { 'foo', 'bar', 'baz' }

    local alpha = function()
      foo()
      bar()
    end

    local beta = function()
      bar()
      baz()
    end

    return
    {
      alpha = alpha;
      beta = beta;
    }

That is, bring to local scope anything what is used by calling import
function (that is a fancy loadfile with cache; it returns requested
keys from the table, returned by imported file; also accepts a table
instead of filename for complex cases; no caching then).

Then define locally whatever content is in that file for (alpha, beta
functions here). Note all unauthorized access to globals is prohibited
in runtime.

Then export public content by returning a table.

This is nice and clean scheme, and I like it.

However there is one weak spot -- the import function. It is too easy
to mess things up and write arguments in wrong order:

    local foo, bar, baz = import 'path/to/file.lua' { 'bar', 'baz', 'foo' }

Above example is a key to a painful debug session (that actually
happened a couple of times).

I dream of following syntax to define all mentioned locals automatically:

   import 'path/to/file.lua' { 'bar', 'baz', 'foo' }

Add/remove braces, quotes etc. to your taste. Consider also Erlang's
"-import(Module,Functions)." as a valid example of this approach in
*another* language.

This is probably the first *real* reason which affects *my* interests
where I'd wish for macros in Lua. I understand of course that it
should be quite simple to implement with token filters / Metalua etc.

Alexander.

Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

David Manura
Alexander Gladysh writes:
>
> I [organize] each file as follows:
>
>     local foo, bar, baz = import 'path/to/file.lua' { 'foo', 'bar', 'baz' }
> ...
> However there is one weak spot -- the import function. It is too easy
> to mess things up and write arguments in wrong order:
>
>     local foo, bar, baz = import 'path/to/file.lua' { 'bar', 'baz', 'foo' }

local Corge = require 'qux.quux.Corge'
local foo = Corge.foo
local bar = Corge.bar
local baz = Corge.baz

There can, however, be an advantage in removing some or all of the last three
lines and, for example, referencing "Corge.foo" rather than "foo" in the code
since the prefix "Corge." indicates explicitly where the function "foo" comes
from, and the Hungarian-ish prefix makes obvious the fact that "foo" is external
to the current module.  The "local foo = Corge.foo" is called a "static import"
in a some other languages and its use is recommended only sparingly[1].

[1] http://java.sun.com/j2se/1.5.0/docs/guide/language/static-import.html




Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

Mark Hamburg-4
on 3/21/08 8:17 PM, David Manura at [hidden email] wrote:

> local Corge = require 'qux.quux.Corge'
> local foo = Corge.foo
> local bar = Corge.bar
> local baz = Corge.baz
> 
> There can, however, be an advantage in removing some or all of the last three
> lines and, for example, referencing "Corge.foo" rather than "foo" in the code
> since the prefix "Corge." indicates explicitly where the function "foo" comes
> from, and the Hungarian-ish prefix makes obvious the fact that "foo" is
> external
> to the current module.  The "local foo = Corge.foo" is called a "static
> import"
> in a some other languages and its use is recommended only sparingly[1].

When used frequently, however, it can be a performance win since it
eliminates a table lookup. I've started to use the convention:

    local Corge_foo = Corge.foo

Mark



Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

Alexander Gladysh
In reply to this post by David Manura
> > However there is one weak spot -- the import function. It is too easy
>  > to mess things up and write arguments in wrong order:
>  >
>  >     local foo, bar, baz = import 'path/to/file.lua' { 'bar', 'baz', 'foo' }
>
>  local Corge = require 'qux.quux.Corge'
>  local foo = Corge.foo
>  local bar = Corge.bar
>  local baz = Corge.baz

Wel, two things.

1. I prefer to avoid using require.
2. There is no validation for Corge.foo to actually exist.

Alexander.

Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

David Manura
Mark Hamburg writes:
> When used frequently, however, it can be a performance win since it
> eliminates a table lookup. I've started to use the convention:
> 
>     local Corge_foo = Corge.foo

Alexander Gladysh writes:

> 1. I prefer to avoid using require.
> 2. There is no validation for Corge.foo to actually exist.

  local M = {}

  local Corge     = dofile 'qux/quux/Corge.lua'  -- or "import"
  local Corge_foo = Corge.foo
  local Corge_bar = Corge.bar
  local Corge_baz = Corge.baz
  -- local Corge_grault = Corge.grault  -- would raise

  local function alpha()
    Corge_foo()
    Corge_bar()
  end
  M.alpha = alpha

  local function beta()
    Corge_bar()
    Corge_baz()
  end
  M.beta = beta

  setmetatable(M, {
    __index =
      function(t,k) error("key not exist: " .. tostring(k)) end
  })
  return M


If you prefer, the "key not exist" function could be defined
apart from the module:

  local Corge_foo = import(Corge, 'foo')    -- or
  local Corge_foo = checked(Corge).foo



Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

Alexander Gladysh
>  > When used frequently, however, it can be a performance win since it
>  > eliminates a table lookup. I've started to use the convention:
>  >
>  >     local Corge_foo = Corge.foo
>
>  > 1. I prefer to avoid using require.
>  > 2. There is no validation for Corge.foo to actually exist.
>
>  <...>
>
>  If you prefer, the "key not exist" function could be defined
>  apart from the module:
>
>   local Corge_foo = import(Corge, 'foo')    -- or
>   local Corge_foo = checked(Corge).foo

My import() function does check for the key existance. What I meant in
the original post is that there is no protection from messing up order
of existant key:

  local foo, bar, baz = import 'path/to/file.lua' { 'bar', 'baz', 'foo' }

If you write it separately, well, yes, it would arguably be somewhat
easier to spot while reading code, but at price of greater code bloat:

  local file = import 'path/to/file.lua' -- Okay, set metatable there
  local foo = file.foo
  local bar = file.bar
  local baz = file.baz

As soon as you fold it in single line for compactness, all benefits vanish:

  local file = import 'path/to/file.lua'
  local foo, bar, baz = file.foo, file.baz, file.baz

Alexander.

Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

Alexander Gladysh
In reply to this post by David Manura
> Alexander Gladysh writes:
>  > I [organize] each file as follows:
>  >     local foo, bar, baz = import 'path/to/file.lua' { 'foo', 'bar', 'baz' }
>  > ...
>  local Corge = require 'qux.quux.Corge'
>  local foo = Corge.foo
>  local bar = Corge.bar
>  local baz = Corge.baz
>
>  There can, however, be an advantage in removing some or all of the last three
>  lines and, for example, referencing "Corge.foo" rather than "foo" in the code
>  since the prefix "Corge." indicates explicitly where the function "foo" comes
>  from, and the Hungarian-ish prefix makes obvious the fact that "foo" is external
>  to the current module.  The "local foo = Corge.foo" is called a "static import"
>  in a some other languages and its use is recommended only sparingly[1].
>
>  [1] http://java.sun.com/j2se/1.5.0/docs/guide/language/static-import.html

As I understand that article, it argues against abuse of import-all construct:

  import static java.lang.Math.*;

Such abuse, I agree, is a bad thing.

But what I use is more like named import with aliases (apologies if
messed terminology):

  import static java.lang.Math.PI;

That is:

  local pi = import 'math.lua' { 'pi' } -- actually, since it is a
global table: import(math) { 'pi' }

Which (careful) use is actually praised in the article.

In case of heavy importing, of course, you may use module-name prefix:

  local math_pi = import 'math.lua' { 'pi' }
  print(math_pi)

Which is equivalent to

  local math = import 'math.lua'
  print(math.pi)

But saves a bit of performance avoiding hash lookup.

Alexander.

Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

Asko Kauppi
In reply to this post by Alexander Gladysh

luaSub's 'using' extension does the following:

--    local sin,cos,tan= using math
--      -->
--    local sin,cos,tan = math.sin, math.cos, math.tan

It would be trivial to make this use 'require'. The reason that variables are named on the left side is a technical one.

-asko


Alexander Gladysh kirjoitti 24.3.2008 kello 12:49:
When used frequently, however, it can be a performance win since it
eliminates a table lookup. I've started to use the convention:

   local Corge_foo = Corge.foo

1. I prefer to avoid using require.
2. There is no validation for Corge.foo to actually exist.

<...>

If you prefer, the "key not exist" function could be defined
apart from the module:

 local Corge_foo = import(Corge, 'foo')    -- or
 local Corge_foo = checked(Corge).foo

My import() function does check for the key existance. What I meant in
the original post is that there is no protection from messing up order
of existant key:

local foo, bar, baz = import 'path/to/file.lua' { 'bar', 'baz', 'foo' }

If you write it separately, well, yes, it would arguably be somewhat
easier to spot while reading code, but at price of greater code bloat:

 local file = import 'path/to/file.lua' -- Okay, set metatable there
 local foo = file.foo
 local bar = file.bar
 local baz = file.baz

As soon as you fold it in single line for compactness, all benefits vanish:

 local file = import 'path/to/file.lua'
 local foo, bar, baz = file.foo, file.baz, file.baz

Alexander.



Reply | Threaded
Open this post in threaded view
|

Re: Autolocals?

Fabien-3
In reply to this post by Mark Hamburg-4
On Sat, Mar 22, 2008 at 9:08 AM, Mark Hamburg <[hidden email]> wrote:
When used frequently, however, it can be a performance win since it
eliminates a table lookup. I've started to use the convention:

   local Corge_foo = Corge.foo

I've modified the metalint hack I've posted a couple of days ago (http://lua-users.org/lists/lua-l/2008-04/msg00082.html), so that it automates this task. More precisely:
- as before, it checks that you only use declared fields in the module, unless you have declared the module as "free"
- it replaces all instances of the module with a local variable
- it declares the corresponding local var at the appropriate place, i.e. either at the beginning of the file for base libraries, or after the relevant require() call for loaded libraries.

To enable autolocal compilation, use option -a. To see the AST after transformation, use -d. You're still advised to use the latest git snapshot of metalua.

An illustrative example:

local x = { }
-- This module declares table 'Corge', 
-- and advertises it in its .dlua interface file:
require 'corge'
table.insert(x, 'foo')
Corge.foo(x)

This is compiled into (tilde characters are used into identifier names, to prevent clashes with legal names):

local x = { }
require 'corge'
local ~Corge~foo = Corge.foo
~table~insert(x, 'foo')
~Corge~foo(x)

New version: http://metalua.luaforge.net/metalint-0.2.tgz. Usual warnings: it's an undertested quick'n dirty hack, which is likely to have bugs in corner cases.