4
\$\begingroup\$

I am writing a couple of functions accepting tables as input parameters. These tables constitute a range of options, which should either be given or inferred from default tables.

Concrete use cases can be found in the unit tests below.

The main question: Is the code idiomatic Lua – especially the use of metatables?

Code

local defvalue = {}
--[[
 Provides a means to fill a table with default options if they are not
 already present.
 This function is based on meta tables and their __index() function.
 table: Your (input) table.
 defTable: A table containing all default values.
 recursive: A boolean indicating if sub-tables should also be bound to the
 values found in defTable.
--]]
function defvalue.bind_table(table, defTable, recursive)
 local mt = {
 __index = function (table, key)
 return defTable[key]
 end
 }
 setmetatable(table, mt)
 if recursive then
 for key, value in pairs(table) do
 if type(value) == "table" then
 defvalue.bind_table(table[key], defTable[key], true)
 end
 end
 end
end
return defvalue

Unit tests (using luaunit):

require('luaunit/luaunit')
local defvalue = require('defvalue')
TestDefaultValue = {} -- class
 function TestDefaultValue:testDefaultValue()
 local actualTable = {}
 local defTable = {
 property = "value"
 }
 defvalue.bind_table(actualTable, defTable, false)
 assertEquals(actualTable["property"], "value")
 end
 function TestDefaultValue:testRecursiveValues()
 local actualTable = {
 -- test merging of sub-tables
 secondSubTable = {
 }
 }
 local defTable = {
 subTable = {
 property = "value"
 };
 secondSubTable = {
 secondSubProperty = "secondSubValue"
 }
 }
 defvalue.bind_table(actualTable, defTable, true)
 assertEquals(actualTable["subTable"]["property"], "value")
 assertEquals(actualTable["secondSubTable"]["secondSubProperty"], "secondSubValue")
 end
 function TestDefaultValue:testNonRecursiveValues()
 local actualTable = {
 subTable = {}
 }
 local defTable = {
 subTable = {
 property = "Hello World!"
 }
 }
 defvalue.bind_table(actualTable, defTable, false)
 assertEquals(actualTable["subTable"]["property"], nil)
 end
 function TestDefaultValue:testDynamicChange()
 local actualTable = {}
 local defTable = {}
 defvalue.bind_table(actualTable, defTable, false)
 assertEquals(actualTable["property"], nil)
 defTable["property"] = 42
 assertEquals(actualTable["property"], 42)
 end
 function TestDefaultValue:testPropertyShadowing()
 local actualTable = {
 property = "value";
 subTable = {
 subProperty = "subValue"
 };
 secondSubTable = {
 secondSubProperty = "secondSubValue"
 }
 }
 local defTable = {
 property = "hello";
 subTable = {
 subProperty = "world"
 };
 secondSubTable = 42
 }
 defvalue.bind_table(actualTable, defTable, true)
 assertEquals(actualTable["property"], "value")
 assertEquals(actualTable["subTable"]["subProperty"], "subValue")
 assertEquals(actualTable["secondSubTable"]["secondSubProperty"], "secondSubValue")
 end
-- class TestDefaultValue
LuaUnit:run()
asked Dec 20, 2014 at 14:18
\$\endgroup\$
11
  • \$\begingroup\$ You can pass value instead of table[key] in defvalue.bind_table(table[key] \$\endgroup\$ Commented Jan 7, 2015 at 0:29
  • \$\begingroup\$ Also, table is a reserved keyword in Lua. Use some other variable name instead :) \$\endgroup\$ Commented Jan 7, 2015 at 0:29
  • \$\begingroup\$ Technically table is simply a default module/global but the same remedy is correct. =) \$\endgroup\$ Commented Jan 9, 2015 at 15:37
  • \$\begingroup\$ It might be good to test that defTable[key] is a table in that recursive loop too. If it isn't the lookup for a default value later might error. \$\endgroup\$ Commented Jan 9, 2015 at 15:40
  • 2
    \$\begingroup\$ If you comment out secondSubTable from actualTable in that test and run it you get an error because you cannot index a number value. That was my point. \$\endgroup\$ Commented Jan 9, 2015 at 17:51

1 Answer 1

3
\$\begingroup\$

If you comment out secondSubTable from actualTable in the testPropertyShadowing test and run it you get an error because you cannot index a number value.

The problem is that you are assuming that the replacement value will be index-able but not all values are. Tables, strings and custom userdata can be indexed but functions, numbers, and nil (by default at least) cannot be.

You might want to consider checking for the default being a table (testing for userdata requires trying it in a pcall I believe and is probably not worth it). Alternatively, you could leave it alone and let people use anything they can index and keep both parts if they use something else.

answered Jan 9, 2015 at 18:38
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.