[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Safe navigation operator
- From: Jerome Vuarand <jerome.vuarand@...>
- Date: Mon, 23 Aug 2010 15:47:22 +0200
2010/8/23 Cuero Bugot <cbugot@sierrawireless.com>:
> I came across a case where I found no short way to write it in Lua. This case could be solved with a "Safe navigation operator" (at least it is the way it is called in Groovy).
>
> The problem happens (for example) when you use multiple level tables to store configurations. For example, you store something like:
> config.network.server.url = "xxx"
> If the parameter is optional, or if you do not want bad surprise, when you want to use it you need to write:
> if confg.network and config.network.server and config.network.server.url then
> dosomething(config.network.server.url)
> End
>
> This is quite verbose, and it could be nice if it returned nil if one of the level of "config.network.server.url" is nil...
> (See Safe navigation operator in groovy)
>
> So the next step was "Well Lua is wonderful, it'll be easy to implement that 'specific' behavior with metamethods!". But after a little though and tries out, I realized that this use case was not coverable with __index / __newindex, because those metamethods lacks a 'context'.
>
> Does somebody already encountered/solved that use case ?
If you don't mind adding a pair of parenthesis (or quotes) to the end
of the expression, you can get something pretty nice with __call :
local wrapper_mt = {}
local function wrapper(root, path)
return setmetatable({
root = root,
path = path,
}, wrapper_mt)
end
local function follow(root, path, create)
for _,elem in ipairs(path) do
local child = root[elem]
if not child then
if create then
root[elem] = {}
else
return
end
end
root = child
end
return root
end
function wrapper_mt:__index(k)
local path = {}
for _,elem in ipairs(self.path) do
table.insert(path, elem)
end
table.insert(path, k)
return wrapper(self.root, path)
end
function wrapper_mt:__newindex(k, v)
local parent = follow(self.root, self.path, true)
parent[k] = v
end
function wrapper_mt:__call(...)
local path = {}
for _,elem in ipairs(self.path) do
table.insert(path, elem)
end
for _,elem in ipairs{...} do
table.insert(path, elem)
end
return follow(self.root, path, false)
end
function wrap(t)
return wrapper(t, {})
end
if ...=='test' then
local a = { b = { c = 42 } }
a = wrap(a)
assert(pcall(function() return a.c() end))
assert(pcall(function() return a.b.b() end))
assert(pcall(function() return a.c.c() end))
assert(pcall(function() return a.c.c.D.e.f() end))
assert(pcall(function() return a.c'c' end))
assert(pcall(function() return a.c.c.D.e'f' end))
assert(pcall(function() return a('c', 'c') end))
assert(pcall(function() return a('c', 'c', 'D', 'e', 'f') end))
assert( nil == a.c() )
assert( nil == a.b.b() )
assert( nil == a.c.c() )
assert( nil == a.c.c() )
assert( nil == a.c.c.D.e.f() )
assert( nil == a.c'c' )
assert( nil == a.c.c.D.e'f' )
assert( nil == a('c', 'c') )
assert( nil == a('c', 'c', 'D', 'e', 'f') )
assert(pcall(function() return a.b.c() end))
assert(pcall(function() return a.b'c' end))
assert(pcall(function() return a('b', 'c') end))
assert( 42 == a.b.c() )
assert( 42 == a.b'c' )
assert( 42 == a('b', 'c') )
print("all tests passed successfully")
end