Depth indexing of nil

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

Depth indexing of nil

Simon Green
Hi all,

I’m relatively new to Lua and I feel like this should have already been answered, but if so I cannot find the right combination of words to type into Google to find it.

Suppose I have a nested structure of tables a to e such that I could have a.b.c.d.e. However, anything from b down might not actually exist. So, ‘a’ could be empty, or ‘b’ could be … all the way down to ‘d’ not having an ‘e’.

I want to use the value of ‘e’, if it exists. If it doesn’t, that’s fine, I do something else. This:

if a.b.c.d.e == something then
    something-else
end

only works if everything down to e exists. If anything else doesn’t, then Lua spits the dummy and tells me I’m indexing a nil value. This is technically correct, but actually, I don’t care. So what I end up having to do is:

if a and a.b and a.b.c and a.b.c.d and a.b.c.d.e and a.b.c.d.e == something then
    something-else
end

In Python, if I referenced a.b.c.d.e and b didn’t exist I’d simply get an IndexError or KeyError exception. Noting that there are no exceptions in Lua, is there some other way I can avoid having thousand-word-long lines every time I want an if statement? Basically, what I'm asking is, if 'b' doesn't exist, is there a way to have a.b.c.d.e == x just return false instead of break my entire program?

Cheers
Simon




Simon Green
Senior VAS Engineer
Two Degrees Mobile Limited
==========================
(M) 022 023 0240
(P) +64 9 919 7057
www.2degreesmobile.co.nz

Two Degrees Mobile Limited | 47-49 George Street | Newmarket | Auckland | New Zealand|
PO Box 8355 | Symonds Street | Auckland 1150 | New Zealand | Fax +64 9 919 7001



Disclaimer
The e-mail and any files transmitted with it are confidential and may contain privileged or copyright information. If you are not the intended recipient you must not copy, distribute, or use this e-mail or the information contained in it for any purpose other than to notify us of the error. If you have received this message in error, please notify the sender immediately, by email or phone (+64 9 919 7000) and delete this email from your system. Any views expressed in this message are those of the individual sender, except where the sender specifically states them to be the views of Two Degrees Mobile Limited. We do not guarantee that this material is free from viruses or any other defects although due care has been taken to minimize the risk




Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Soni "They/Them" L.


On 08/07/15 10:10 PM, Simon Green wrote:

> Hi all,
>
> I’m relatively new to Lua and I feel like this should have already been answered, but if so I cannot find the right combination of words to type into Google to find it.
>
> Suppose I have a nested structure of tables a to e such that I could have a.b.c.d.e. However, anything from b down might not actually exist. So, ‘a’ could be empty, or ‘b’ could be … all the way down to ‘d’ not having an ‘e’.
>
> I want to use the value of ‘e’, if it exists. If it doesn’t, that’s fine, I do something else. This:
>
> if a.b.c.d.e == something then
>      something-else
> end
>
> only works if everything down to e exists. If anything else doesn’t, then Lua spits the dummy and tells me I’m indexing a nil value. This is technically correct, but actually, I don’t care. So what I end up having to do is:
>
> if a and a.b and a.b.c and a.b.c.d and a.b.c.d.e and a.b.c.d.e == something then
This is SLOW! the fast way would be:

local x = a and a.b
x = x and x.c -- x = a.b; x.c = a.b.c
x = x and x.d
x = x and x.e
if x and x == something then
   -- do stuff
end

Another SLOW option is to wrap it in a function like so:

local function f(a) return a.b.c.d.e end -- cache this somewhere so you
avoid creating/looking up a closure

if pcall(f, a) and f(a) == something then
   -- do stuff
end

>      something-else
> end
>
> In Python, if I referenced a.b.c.d.e and b didn’t exist I’d simply get an IndexError or KeyError exception. Noting that there are no exceptions in Lua, is there some other way I can avoid having thousand-word-long lines every time I want an if statement? Basically, what I'm asking is, if 'b' doesn't exist, is there a way to have a.b.c.d.e == x just return false instead of break my entire program?
>
> Cheers
> Simon
>
>
>
>
> Simon Green
> Senior VAS Engineer
> Two Degrees Mobile Limited
> ==========================
> (M) 022 023 0240
> (P) +64 9 919 7057
> www.2degreesmobile.co.nz
>
> Two Degrees Mobile Limited | 47-49 George Street | Newmarket | Auckland | New Zealand|
> PO Box 8355 | Symonds Street | Auckland 1150 | New Zealand | Fax +64 9 919 7001
>
>
>
> Disclaimer
> The e-mail and any files transmitted with it are confidential and may contain privileged or copyright information. If you are not the intended recipient you must not copy, distribute, or use this e-mail or the information contained in it for any purpose other than to notify us of the error. If you have received this message in error, please notify the sender immediately, by email or phone (+64 9 919 7000) and delete this email from your system. Any views expressed in this message are those of the individual sender, except where the sender specifically states them to be the views of Two Degrees Mobile Limited. We do not guarantee that this material is free from viruses or any other defects although due care has been taken to minimize the risk
>
>
>
>

--
Disclaimer: these emails are public and can be accessed from <TODO: get a non-DHCP IP and put it here>. If you do not agree with this, DO NOT REPLY.


Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Luiz Henrique de Figueiredo
In reply to this post by Simon Green
Use
        debug.setmetatable(nil, {__index = {}})

See http://lua-users.org/lists/lua-l/2010-05/msg00159.html

Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Daurnimator
On 9 July 2015 at 12:12, Luiz Henrique de Figueiredo
<[hidden email]> wrote:
> Use
>         debug.setmetatable(nil, {__index = {}})
>
> See http://lua-users.org/lists/lua-l/2010-05/msg00159.html
>

Just remember to set it back afterwards!

Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Soni "They/Them" L.
In reply to this post by Luiz Henrique de Figueiredo


On 08/07/15 11:12 PM, Luiz Henrique de Figueiredo wrote:
> Use
> debug.setmetatable(nil, {__index = {}})
>
> See http://lua-users.org/lists/lua-l/2010-05/msg00159.html
>
I should make a library for this that doesn't require debug.

Maybe something like "local f = mkgetter("a", "b", "c", ...)" or "local
f = mkgetter().a.b.c()" and then "if f(obj) then", maybe even return the
depth as a second return so like local value, depth = f(obj), depth
could be negative if the second arg was true as in f(obj, true).

It obviously won't use pcall as that's slow. Might use tail calls
because I love tail calls. :P

--
Disclaimer: these emails are public and can be accessed from <TODO: get a non-DHCP IP and put it here>. If you do not agree with this, DO NOT REPLY.


Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Peter Pimley
In reply to this post by Simon Green
On 9 July 2015 at 02:10, Simon Green <[hidden email]> wrote:
Hi all,

I’m relatively new to Lua
 [...]
Noting that there are no exceptions in Lua.
 [...]
Cheers
Simon

If you're 1) new to Lua, from some C++/C# like language, 2) not mega-concerned about speed and memory allocations, and 3) Not ready to think about metatables, tail calls and other advanced features mentioned in previous answers, then IMO a variation of Soni L's first answer is the most readable for noobs:

local thing = nil
pcall (function() thing = a.b.c.d.e end) -- swallows the error, thing remains nil
if thing == something then
  something-else
end

You say "Noting that there are no exceptions in Lua".  There are errors, which are very much like exceptions.  They can be caught with pcall.

Peter
Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Scott Morgan
In reply to this post by Simon Green
On 09/07/15 02:10, Simon Green wrote:
> if a.b.c.d.e == something then something-else end
>
> only works if everything down to e exists. If anything else doesn’t,
> then Lua spits the dummy and tells me I’m indexing a nil value. This
> is technically correct, but actually, I don’t care.

This is my biggest problem with Lua, makes working with optional tables
a real pain and all the solutions have problems; debug.setmetatable is
overkill (I think most people only want this on some table accesses, not
all), and the others are clunky at best.

It was proposed a while back adding an extra operator for safe indexing,
something like a?b?c?d I forget the conclusion of that debate, but would
like to petition for something along those lines.

Scott

Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Roberto Ierusalimschy
In reply to this post by Simon Green
> I want to use the value of ‘e’, if it exists. If it doesn’t, that’s fine, I do something else. This:
>
> if a.b.c.d.e == something then
>     something-else
> end
>
> only works if everything down to e exists. If anything else doesn’t, then Lua spits the dummy and tells me I’m indexing a nil value. This is technically correct, but actually, I don’t care. So what I end up having to do is:
>
> if a and a.b and a.b.c and a.b.c.d and a.b.c.d.e and a.b.c.d.e == something then
>     something-else
> end

My prefered technique is this one:

  -- put this somewhere in your code; it can be global or local:
  E = {}

  if ((((a or E).b or E).c or E).d or E).e == something then ...


-- Roberto

Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Javier Guerra Giraldez
On Thu, Jul 9, 2015 at 9:35 AM, Roberto Ierusalimschy
<[hidden email]> wrote:
>   -- put this somewhere in your code; it can be global or local:
>   E = {}
>
>   if ((((a or E).b or E).c or E).d or E).e == something then ...


be careful not to forget the last '.e', or your variable can alias E,
with funny results if you start populating it.  (i'm sure somebody
will advice on setting a __newindex to block that)

--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Scott Morgan
In reply to this post by Roberto Ierusalimschy
On 09/07/15 15:35, Roberto Ierusalimschy wrote:
> My prefered technique is this one:
>
>   -- put this somewhere in your code; it can be global or local:
>   E = {}
>
>   if ((((a or E).b or E).c or E).d or E).e == something then ...

-- somewhere, e.g. module
local nothing = setmetatable({}, {
        __index = function(nothing)
                return nothing
        end,
        __call = function()
                return nil
        end,
})

function maybe(tab)
        return setmetatable({}, {
                __index = function(_, key)
                        if tab[key] then
                                return maybe(tab[key])
                        else
                                return nothing
                        end
                end,
                __call = function()
                        return tab
                end,
        })
end

-- then

a = { b = { c = 123 } }

print( maybe(a).b.c() )  -- 123
print( maybe(a).x.c() )  -- nil

--

I'm not 100% happy with it, but I think it'd cover most my use cases.

Could possibly add a __eq function so 'maybe(a).b.c == 123' works as
expected.

Scott


Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Roberto Ierusalimschy
> -- somewhere, e.g. module
> local nothing = setmetatable({}, {
> __index = function(nothing)
> return nothing
> end,
> __call = function()
> return nil
> end,
> })
>
> function maybe(tab)
> return setmetatable({}, {
> __index = function(_, key)
> if tab[key] then
> return maybe(tab[key])
> else
> return nothing
> end
> end,
> __call = function()
> return tab
> end,
> })
> end
>
> -- then
>
> a = { b = { c = 123 } }
>
> print( maybe(a).b.c() )  -- 123
> print( maybe(a).x.c() )  -- nil
>
> --
>
> I'm not 100% happy with it, but I think it'd cover most my use cases.

When all fields are present, that code will create two new tables plus
two new closures at each dot: for instance, maybe(a).b.c() creates four
new objects. That sounds quite expensive.

-- Roberto

Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Scott Morgan
On 09/07/15 17:16, Roberto Ierusalimschy wrote:
...<snip>...

>> a = { b = { c = 123 } }
>>
>> print( maybe(a).b.c() )  -- 123
>> print( maybe(a).x.c() )  -- nil
>>
>> --
>>
>> I'm not 100% happy with it, but I think it'd cover most my use cases.
>
> When all fields are present, that code will create two new tables plus
> two new closures at each dot: for instance, maybe(a).b.c() creates four
> new objects. That sounds quite expensive.


Yeah, you wouldn't want it in any performance code, which is the main
reason I'm not happy with it. Fortunately for me raw performance isn't
an issue where it might be used, where as readability (and ease of
editing) is.

Please do the a?b?c idea in 5.4/6.0 :)

Scott


Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Parke
In reply to this post by Roberto Ierusalimschy
>> -- somewhere, e.g. module
>> local nothing = setmetatable({}, {
>>       __index = function(nothing)
>>               return nothing
>>       end,
>>       __call = function()
>>               return nil
>>       end,
>> })
>>
>> function maybe(tab)
>>       return setmetatable({}, {
>>               __index = function(_, key)
>>                       if tab[key] then
>>                               return maybe(tab[key])
>>                       else
>>                               return nothing
>>                       end
>>               end,
>>               __call = function()
>>                       return tab
>>               end,
>>       })
>> end
>>
>> -- then
>>
>> a = { b = { c = 123 } }
>>
>> print( maybe(a).b.c() )  -- 123
>> print( maybe(a).x.c() )  -- nil
>>
>> --
>>
>> I'm not 100% happy with it, but I think it'd cover most my use cases.

On Thu, Jul 9, 2015 at 9:16 AM, Roberto Ierusalimschy
<[hidden email]> wrote:
>
> When all fields are present, that code will create two new tables plus
> two new closures at each dot: for instance, maybe(a).b.c() creates four
> new objects. That sounds quite expensive.

But it does not have to be that expensive.

do

  local protect     =  false
  local nothing_mt  =  {}
  local nothing     =  setmetatable ( {}, nothing_mt )
  local follow

  function nothing_mt.__index ( t, k )
    if follow then  follow  =  follow [ k ]  end
    return nothing  end

  function nothing_mt.__call ()
    local t  =  follow
    follow   =  nil
    protect  =  false
    return t  end

  function maybe ( t )
    assert ( not protect )
    protect  =  true
    follow   =  t
    return nothing  end

end

a = { b = { c = 123 } }

print ( maybe (a).b.c() )
print ( maybe (a).x.c() )

In the above, the assert will fail if maybe is called again before
__call.  But that could probably be fixed as well, such that only one
new closure would be needed for each nested maybe call.  And those
closures could be cached and reused.

-Parke

Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Petite Abeille
In reply to this post by Simon Green

> On Jul 9, 2015, at 3:10 AM, Simon Green <[hidden email]> wrote:
>
> Basically, what I'm asking is, if 'b' doesn't exist, is there a way to have a.b.c.d.e == x just return false instead of break my entire program?

Sure, if this is something you do often, that’s what convenience functions are for, e.g. GetPath( aTable, ‘a.b.c.d.e.f.g’ ), which will break the path on the dots, and try to resolve the successive keys, returning whatever it can reach. No point of overthinking it.
Reply | Threaded
Open this post in threaded view
|

Re: Depth indexing of nil

Simon Green
In reply to this post by Simon Green

> From: Luiz Henrique de Figueiredo <[hidden email]>

> Subject: Re: Depth indexing of nil

 

> Use

>             debug.setmetatable(nil, {__index = {}})

 

> See http://lua-users.org/lists/lua-l/2010-05/msg00159.html

 

Ah, thankyou! This looks like exactly what I want. Thankyou to the other responders also!



Simon Green
Senior VAS Engineer
Two Degrees Mobile Limited
===========================
(M) 022 023 0240
(P) +64 9 919 7057
www.2degreesmobile.co.nz

Two Degrees Mobile Limited | 47-49 George Street | Newmarket | Auckland | New Zealand |
PO Box 8355 | Symonds Street | Auckland 1150 | New Zealand | Fax +64 9 919 7001


   


Disclaimer
The e-mail and any files transmitted with it are confidential and may contain privileged or copyright information. If you are not the intended recipient you must not copy, distribute, or use this e-mail or the information contained in it for any purpose other than to notify us of the error. If you have received this message in error, please notify the sender immediately, by email or phone (+64 9 919 7000) and delete this email from your system. Any views expressed in this message are those of the individual sender, except where the sender specifically states them to be the views of Two Degrees Mobile Limited. We do not guarantee that this material is free from viruses or any other defects although due care has been taken to minimize the risk