Honoring the goal to make/keep Lua more simple on of the more simple -
that is realistic - ideas is the write a lua constraints module. No
witchcraft, just some module that uses metatables to constraint tables
and thus in the style of defensive programming keep catching errors
early and easy. Constraints is something the user can put up a table
and that will raise an error if violated. In the product they can
easily be removed to minimalst impact by setting constraint =
function() end
constraint(t, "list") -- will error() if any key is no integer and not
from 1..n+1 or a nil is inserted.
constraint(t, "positive keys are list") -- keys that are positive
numeric must be from 1..n+1
constraint(t, "key in {next, foo, bar}") -- a key must be "next" or
"foo" or "bar". Used for 'object' tables, typos raise errors instead
of mysterious nils.
constraint(t, "value is positive") -- all values for t must be
positive integers.
constraint(t, "readonly") -- after this table may not be altered anymore
constraint("no nil globals") -- no global may be nil, any access to an
undefined global will error()
constraint("no new globals") -- after this call no globals may be created.
etc.
This would be a pure lua implementation. I suppose defining its own
grammar as argument is the best I could think of.
I would use it in my project Lsyncd. I started soemthing like this but
somehow let it go, since I didn't wanted to put more effort into the
actual code than the typechecking one.
What do you think about this?
I agree, a single function for establishing constraints. Entirely
doable I think!
Text rules are fine, providing the grammar is quite clear. Otherwise
one can use a constraint-builder pattern but things then aren't so
simple - text processing really is nice & simple with Lua.
Who wants to do a trial implementation? I will look at what's
involved for Lua 5.1.
This is one approach I had to the problem of 'strict tables' [1]
steve d.
Here's another spin; a constraint can be on values, keys or both, with
semi-colon as separator - both the domain and range can be
constrained.
So e.g.
"list; positive" ('positive' implies 'number')
"array; table"
"{alpha,beta,gamma}; {number,number,number}"
"list; readonly" (I imagine that ";readonly" means that this is a
value-only constraint)
We're avoiding the traditional and vexing idea of 'classes'; what _is_
lost by not making classes is runtime type info, but I don't know
whether this is really important anyway.
Naturally the constraint() function needs to check whether the
existing table satisfies the constraint, which is a useful side-effect
(like how Dirk checks whether his initial list is free of holes.)
steve d.
semicolon as separator is a good idea and easy to implement with gmatch.
> "array; table"
> "{alpha,beta,gamma}; {number,number,number}"
I don't get my head quite around what is ment, not newbie friendly :-)
I see two ways to go I'm not quite sure which is the better one.
One is to have the strings feel somehow "natural language", like "keys
are positive", "values are non-negative", etc.
which has the advantage to be supereasy to read, but the downside that
the "parser-AI" needs to be tought more or less everything you want it
do.
The other is some arithmetic expression that gets parsed and executed
(possibly by the lua parser)
"key >= 0"
"if key < n then condition = key ~=nil end"
where condition is tested to be true or false or if not present the
top most value on stack is used.
I suppose for testing I should try to implement both and see which feels better.
Maybe we can actually do both like either the more "basic sentences" or
"condition: key % 2 == 0" as one valid sentence, where the part after
the doublecolon is parsed and the chunk then executed by lua on every
read/write.
Testing if the existing table fullfills the constraint when applying
to it makes of course a lot of sense.
end - axel
D.
That's the test of any made-up notation; the WTF factor ;) I meant
that the keys had to be {alpha,beta,gamma} and that the
_corresponding_ types were 'number' etc. That is, specifying a
discrete map. But then why not just
'{alpha=number,beta=number,gamma=number}" - that looks more lua-ish ?
> One is to have the strings feel somehow "natural language", like "keys
> are positive", "values are non-negative", etc.
> which has the advantage to be supereasy to read, but the downside that
> the "parser-AI" needs to be tought more or less everything you want it
It is a little tricky; makes me think about COBOL, which was meant to
make programming as easy as writing English (heh). But in practice
COBOL statements have very particular wording. I still like the idea
of 'plain text' constraints however, although there should be a
limited number of possible sentence structures.
As for parsing expressions; remember that such expressions will also
need to be evaluated at runtime. Can be done efficiently, but
requires dynamic code generation for constraints. I did wonder about
this (i.e 'value > 0' rather than 'value positive') but perhaps then
constraints become over-complicated and hard to understand?
steve d.
PS. parsing stuff is a lot of fun!
I think lpeg is a fine thing, but like Hisham (of LuaRocks fame) I
find that by the time I've wrapped my head around it, I've forgotten
the original problem. String patterns can do all this, without any
external dependencies.
steve d.
That looks good. You can of course overload constraint(t,c) - if c is
a table then it's interpreted as a prototype directly.
So a constraint can be a sentence of the type: [adjective] (subject)
"are" [not] (adjective)
adjectives := positive, negative, strings, integer, tables, in set [table]
subject := keys, values
Or a prototype with keys and values being adjective, thus for every
key->value part of the table the value must fullfill the condition of
the adjective.
Or a condition: starting with "condition:" and the rest is parsed by
the lua parser in the newindex method
where "key" is key "value" is value and "n" is #table.
The assumption if not presumption that someone's English is good
enough to include an accurate distinction between verb forms are
certainly not universally true even among contributors to lua-l.
(Do you think that "are" should have been "is"? Are you sure?)
I can forgive people who do not know Lua for inventing their own
configuration language. But really, whatever words we use for
the constraints, the one skill we are surely entitled to assume
from the user is basic Lua literacy.
So I suggest constraint syntax along the lines of:
strict_lists = true
strict_lists = false
strict_lists = nil -- whatever happens to be the default setting
mysetting = save_constraints() -- save current setting of constraints
reset_constraints() -- reset all constraints to the default setting
reset_constraints(my_setting) -- ... to my_setting
Dirk
This is a good point. The Lua way would look like this:
constraint(t,{
strict_list = true;
values = 'positive'
})
No parsing involved, just a little bit of intelligence to interpret
possible values like 'positive','number',etc. More involved
constraints can be given as an anonymous function.
> mysetting = save_constraints() -- save current setting of constraints
> reset_constraints() -- reset all constraints to the default setting
> reset_constraints(my_setting) -- ... to my_setting
Don't like that - anything that requires mucking around with global
state is going to be awkward and can break in interesting ways.
COBOL is apparently not as dead as we would like. I hear that Java is
the new COBOL, however ;)
steve d.
Dirk
Yes true that. That might be better. I dissed this first, since I
considered it a problem every key to be able to present only once, so
what to do with multiple conditions on keys or values? But this can
simply be a subtable.
so valid constraint tables are
{ values = {'positive', 'rule: key % 2 == 0'} } --> all keys are
positive and even
or
{ positive = 'list' } --> all positive keys are a list
or
{ positive_integer = 'list' } --> all positive integer keys are a
list, (e.g. 1.5 is allowed here what isnt in the one before)
or
{ 'c:key % 2' = 'strings' } --> all even keys must be strings
or
{ 'proto' = {'alpha' = 'string', 'beta' = 'string', 'gamma' = string',
[constraint.others] = nil }
alpha, beta, gamma must be strings, no other values allowed. (Not sure
how to make the others syntactically nice)
I dislike globals too. Every table should have its constraints applied
by itself.
What we could think of is an optional core patch that complains/warns
when '#' is called on any table that is not constrained in a list.
Nice for debugging if you dont use libraries that do that.
We too could optionally hijack table.remove and table.insert to make that test.
for conditions yes, otherwise I dont see how "values are positive" can be made to properly be parsed by lua. a custom parser that looks for [adjective] (subject) are [not] (adjective) is easy enough.
Do you mean complicated in execution or complicated in the user interface?
"Natively" needs a core patch and in my opinion a lua only module
using metatables is more easy on it to get started and accepted.
Kind regards,
Axel