This is a list of things about writing Lua modules that I had to figure out the hard way.

Call functions that expect a frame object

edit

To call a function that expects a frame object, you need to give it a table containing an args table like so: WikidataIB.getValue{ args = {'P31', qid='Q42'} }. Prefer functions that take arguments directly, e.g. WikidataIB._getValue, over those that expect a frame object.

Get user's language on multilingual wikis

edit

Scribunto does not provide a Lua interface to get the user's language code ("en"), so we need to call a parser function:

frame:callParserFunction( "int", "lang" )

This only works on wikis where subpages of MediaWiki:Lang are set up. Wikimedia Commons is such a wiki.

Performance

edit

Don't be that guy who stops caring about performance because "premature optimization is the root of all evil". There are several easy ways to optimize Lua code without compromising readability.[1][2]

  1. Always use local variables and functions.
  2. Do not build long strings using repeated concatenations. Add the string pieces to a table and use table.concat() at the end.[3]
  3. Use t[#t+1] = v to add a value to a table instead of table.insert(t, v) to avoid function overhead.
  4. Do not create the same closure or table in every loop iteration. Define it outside the loop.
  5. Lua rehashes a table if its size exceeds a power of two after adding an element.[4] So don't do this:
    local args = {}
    args[1] = 'P31'
    args.qid = 'Q42'
    _getValue( args )
    
    Do this instead:
    _getValue{ 'P31', qid = 'Q42' }
    
    If you are creating lots of empty tables that each need room for n elements, initialize it with n nil values: local args = { nil, nil, nil }.

Optimizations that are not worth it

edit

The following optimizations are generally not worth going for because they add a bit of noise to the code:

Slow Fast
Adding to table inside loop
for i = 1, 99999 do
    t[#t+1] = i
end
local len = #t + 1
for i = 1, 99999 do
    t[len] = i
    len = len + 1
end
Iterating over a sequence table
for i, v in ipairs(t) do
    ···
end
for i = 1, #t do
    local v = t[i]
    ···
end
Calling a library function repeatedly
for i = 1, 99999 do
    math.sin(i)
end
local sin = math.sin
for i = 1, 99999 do
    sin(i)
end

Benchmarking

edit

You can do benchmarks from the Lua console with

local s = os.clock()
-- your code here
print('took ' .. os.clock() - s .. ' seconds')

Handling nil values

edit

Be careful with variables that can be nil: Indexing a nil table or trying to iterate over it with ipairs() will result in an error:

Bad Good
v = claim.mainsnak.datavalue.value
-- error if claim or datavalue are nil
v = claim and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value
if v then
  -- do something with v
end
t = getTableOrNil()
for i,v in ipairs(t) do
t = getTableOrNil() or {}
for i,v in ipairs(t) do

When working with Wikidata items, make sure that your code does not produce errors if you give it an empty item. Another common source of nil errors are somevalue and novalue claims, where mainsnak.datavalue == nil.

edit

References

edit
  1. StackOverflow answer by dualed. Retrieved June 3, 2022.
  2. Lua Performance on the Spring Wiki. Retrieved June 3, 2022.
  3. Programming in Lua, chapter 11.6. lua.org.
  4. Lua Performance Tips (PDF) by Roberto Ierusalimschy