patternMinor
Default value mechanism in Lua using metatables
Viewed 0 times
metatablesvaluedefaultmechanismusinglua
Problem
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
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"]
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 defvalueUnit 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"]
Solution
If you comment out
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
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.Context
StackExchange Code Review Q#74283, answer score: 3
Revisions (0)
No revisions yet.