[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Lua Style Guide ?
- From: nobody <nobody+lua-list@...>
- Date: Tue, 6 Jun 2017 18:17:53 +0200
On 06/06/2017 12:38 AM, Sean Conner wrote:
[1] I may allow such use for single use functions for modules, but
then again, I do modules a bit differently [2] to maintain easy
compatability with Lua 5.1.
[2] Check out
https://github.com/spc476/lua-conmanorg/blob/master/lua/date.lua for
an example.
On modules / globals: This is an important aspect of code style &
organization that has a big impact on how you can interact with the code
and is hard to change later. Most common styles rely heavily on
`local`s / upvalues, which (IMHO) negatively impacts debuggability &
flexibility.
As an example, let's say you have public `foo.unparse`, which uses
(among many others) internal `readDateTime` which uses internal
`readTime` (which happens to contain an arithmetic bug – it returns
slightly wrong values of the right type, so there's no nice stack
trace). Your tests tell you that `unparse` gives wrong results. Among
the dozens of internal functions, how do you localize the bug?
To test the pieces – the internal functions – (whether by test scripts
or in the REPL) you can (temporarily) edit the source and either put
your test code in there (so no REPL for you) or remove the `local`
annotations (yuck!), or you traverse the chain of upvalues. If you add
metatable magic on functions' `__index`, this can be as "nice" as
`foo.unparse.readDateTime.readTime`, otherwise it may be as terrible as
select(2,debug.getupvalue(select(2,debug.getupvalue(foo.unparse,1)),2)).
Both are brittle: Code changes may change the path you have to take, you
often have to traverse several levels of upvalues, … I can't think of a
better way to access the parts (and I don't think there is one…)
How do people using these module styles deal with that? Is it actually
a problem or are you (e.g.) using a completely different debugging
approach? (How/) Do you test internal functions? (How/) Do you REPL?
I try to avoid the problem by giving each module its own module
environment (separate from the module table), usually starting modules
with (5.1-5.3 compatible)
local _ENV = setmetatable( { _M = { } }, { __index = _ENV or _G } )
if setfenv then setfenv( 1, _ENV ) end
_M._MENV, package.loaded[...] = _ENV, _M
(_M is the module table, expose stuff by adding to _M. _ENV / _M._MENV
is the module environment and __index-es into the parent environment.
Don't `local` by default, put stuff in the module environment. You can
additionally `local` for speed where needed, as in `foo = …; local foo =
foo` or `local foo = …; _ENV.foo = foo`.)
This avoids both the problem of accidental non-`local`s becoming global
or public and makes the environment easily accessible for debugging.
(If you prefer, omit the `_M._MENV = _ENV` and `debug.getupvalue` the
environment (once, from (almost) any exposed function) for debugging.)
(Manually setting `package.loaded[modname]` is another thing that I see
rarely. If you do this, you don't have to return the module at the end
– which means all module management happens in one place (at the top),
not two (top and bottom). It also permits module "foo" to `require
"foo.bar"`, which can then `require "foo"`, without spinning in a
require-loop.)
Another nice property is that submodules can share their environment –
e.g. in "foo.bar" start instead with `local _ENV = require "foo"._MENV`.
(This is really nice for "util" modules – they can simply populate the
module environment and don't have to add a contrived dummy package.
It's also much easier to split a single module's implementation across
several files. Etc. pp. – with this style/approach, the module
environment is not just a bunch of scattered upvalues, it's an actual
environment/table that can easily be queried/manipulated/…)
-- nobody