Lua structs

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

Lua structs

spir ☣
Hello,

It came to my mind that it is easy in Lua to have kinds of structs, objects with
predefined field structure that they thus do not need to hold themselves,
instead they just hold data. like C structs, Pascal records, Python namedtuples
(actually unnamed). Is there any common or known implementation of such things
in Lua? I have no idea whether it can be useful in practice. Below an example
realisation with 2 usage examples. It basically trades direct field access for
space.

My method is a kind of mixture of object type with proxy table. The diff beeing
that the proxy here in fact holds data and just misses field names. This
implementation also raised a question that can probably be relevant in other
cases of abstraction via metatables: When looking up a field (for get or set),
we need to jump twice from the record (table of field data) to its
"kind"/metatable: first to check there is an index/newindex metafield on the
metatable, second in the actual get/set func to search for field name on the
"kind". What do you think? Is there a way to avoid that double access?

Another question I take the opportunity to ask is about the '%q' format tag: I
initially used to to print field data when they are strings (since the output is
here programmer feedback) but realised that newlines are not escaped. Tabs and
other control chars are ascaped, but newline are just preceded by a _literal_
backslash \. I find this extremely annoying. (Even more because when trying to
"repair" that "default" I realised I cannot even reuse '%q' either before of
after a gsub('\n', '\\10') because the original newline or replacement escape is
itself processed by '%q'; so that '%q' must be replaced alltogether. Or I miss a
point.)

Denis

PS: After reflexion I realised that with strings interned repetition of field
names on "object-like tables" in Lua is just adding a load of refs, not of
string data. Still isn't it a bit wasteful? And tere are tons of cases where
objects a given kind have a constant, predefined structure (well, we could even
say it's the norm).
Also, I learned "small" string are interned: is there a known or even official
order of magnitude of size in bytes?

=======================================================

-- Record "type"

local putit = table.insert    -- put it (item) in array ;-)

Record = {
    mt = {} ,
}

Record.new_kind = function (Record, kind)
    -- reverse map field-name --> index
    kind.field_indexes = {}
    for i,field_name in ipairs(kind) do
       kind.field_indexes[field_name] = i
    end
    -- metafields
    kind.__tostring   = Record.tostring
    kind.__index      = Record.get
    kind.__newindex   = Record.set
    setmetatable(kind, Record)
    return kind
end

Record.new = function (kind, rec)
    setmetatable(rec, kind)
    return rec
end

Record.get = function(rec, field_name)
    local kind = getmetatable(rec)
    local i = kind.field_indexes[field_name]
    return rec[i]
end

Record.set = function(rec, field_name, val)
    local kind = getmetatable(rec)
    local i = kind.field_indexes[field_name]
    rec[i] = val
end

Record.tostring = function (rec)
    local field_names = getmetatable(rec)
    local strings = {}
    for i,item in ipairs(rec) do
       putit(strings, field_names[i] .. ":" .. tostring(item))
    end
    local string = table.concat(strings, ' ')
    return '(' .. string .. ')'
end

Record.__call     = Record.new
Record.mt.__call  = Record.new_kind
setmetatable(Record, Record.mt)

-- exemples
local RGB = Record{'r','g','b'}
local rgb = RGB{2,1,3}
print(rgb)
print(rgb.g)
rgb.g = 9
print(rgb.g)
print(rgb)
print()

local Contact = Record{"phone","email","address"}
local foobar = Contact{"12.34.56.78.90", "[hidden email]", "blah,blah,blah"}
print(foobar)
foobar.email = "[hidden email]"
print(foobar.email)
print(foobar)



Reply | Threaded
Open this post in threaded view
|

Re: Lua structs

steve donovan
On Fri, Nov 16, 2012 at 12:39 PM, spir <[hidden email]> wrote:
> It came to my mind that it is easy in Lua to have kinds of structs, objects
> with predefined field structure that they thus do not need to hold
> themselves, instead they just hold data. like C structs, Pascal records,
> Python namedtuples (actually unnamed). Is there any common or known
> implementation of such things in Lua?

I also thought it was useful to have structs:

http://lua-users.org/wiki/StrictStructs

altho the constructor still uses the field names (easy enough to make
constructor to accept values but then the struct def needs to be
ordered)

The main benefits are:
  - a typo in a fieldname _read_ access is an error, not a silent nil
  - each struct type is unique and can be used in documentation

For a little loss of speed (as you say) we can use the proxy pattern
and then also make it an error to write to an unknown field

steve d.

Reply | Threaded
Open this post in threaded view
|

Re: Lua structs

Doug Currie

On Nov 16, 2012, at 6:28 AM, steve donovan <[hidden email]> wrote:

> altho the constructor still uses the field names (easy enough to make
> constructor to accept values but then the struct def needs to be
> ordered)

Because good terminology is so important in our field, I'll interject that in Lisp, when records are constructed By Order of Arguments (BOA), the function is called a "boa-constructor." :)

e


Reply | Threaded
Open this post in threaded view
|

Re: Lua structs

steve donovan
In reply to this post by spir ☣
On Fri, Nov 16, 2012 at 12:39 PM, spir <[hidden email]> wrote:
> local Contact = Record{"phone","email","address"}

Latest version of the 'strict struct' pattern described in the wiki
can now also support this constructor form.

But you have to define it as an ordered map:

struct.Contact {
   {phone = 0},
   {email = 'text'},
   {address = 'text'}
}

foo = Contact(2344344,'[hidden email]','blah blah')  -- note not using
table ctor here!

There is also an option to use proxy tables, in which case you can
catch type errors on field assignment.

BTW, I'm using a somewhat generalized version of type() here - see the
function _type in the code, exported as struct.type. This returns
_name field of the metatable if it exists.

The values of the keys (whether the keys are ordered or not) serve a
double purpose: providing sensible defaults, and type checking. It
would be a good idea to extend the concept of type matching as Geoff
has suggested so that we can insist that a value is a number between 1
and 10, or a string matching some pattern.

I am not happy about one thing - currently it exports the struct names
into the global table.  I'm starting to think that this is a mistake,
even though a redundant syntax 'Foo = struct.Foo{...}' becomes
necessary if one wants Structs with Names.

Have a look at test-struct.lua for an executable manual ;)

https://gist.github.com/4110502

steve d.

Reply | Threaded
Open this post in threaded view
|

Re: Lua structs

Tim Caswell
I like to use the structs provided by the ffi from luajit (I believe it can be used as an external module in normal lua).  They are real C structs in the backend and as such are much easier on the system resource-wise.  You can even have real typed arrays with any custom struct you want. See the example on C data structures at http://luajit.org/ext_ffi.html


On Mon, Nov 19, 2012 at 7:09 AM, steve donovan <[hidden email]> wrote:
On Fri, Nov 16, 2012 at 12:39 PM, spir <[hidden email]> wrote:
> local Contact = Record{"phone","email","address"}

Latest version of the 'strict struct' pattern described in the wiki
can now also support this constructor form.

But you have to define it as an ordered map:

struct.Contact {
   {phone = 0},
   {email = 'text'},
   {address = 'text'}
}

foo = Contact(2344344,'[hidden email]','blah blah')  -- note not using
table ctor here!

There is also an option to use proxy tables, in which case you can
catch type errors on field assignment.

BTW, I'm using a somewhat generalized version of type() here - see the
function _type in the code, exported as struct.type. This returns
_name field of the metatable if it exists.

The values of the keys (whether the keys are ordered or not) serve a
double purpose: providing sensible defaults, and type checking. It
would be a good idea to extend the concept of type matching as Geoff
has suggested so that we can insist that a value is a number between 1
and 10, or a string matching some pattern.

I am not happy about one thing - currently it exports the struct names
into the global table.  I'm starting to think that this is a mistake,
even though a redundant syntax 'Foo = struct.Foo{...}' becomes
necessary if one wants Structs with Names.

Have a look at test-struct.lua for an executable manual ;)

https://gist.github.com/4110502

steve d.