Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ MUSHclient ➜ Lua ➜ Using metatables to make attempts to index nil return false

Using metatables to make attempts to index nil return false

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Bobble   Canada  (76 posts)  Bio
Date Fri 28 Nov 2008 04:09 AM (UTC)
Message
Greets everyone,

I've run into a bit of a snag and I believe that metatables may hold the answer for me. However, I'm a little unsure how to go about getting what I need done.

Part of one of my functions looks something like this:


if characters_online.elf.warrior then
   Note ("Yay, there's at least one elf warrior online!")
end


The problem is, with the way I've set up my tables, if there's no elves online at all the characters_online.elf key doesn't exist and if this function is called I get an error stating that I tried to index a nil value.

What I would prefer is to have a situation where if the characters_online.elf key is nil, it returns a boolean false rather than an error.

Does anyone have any idea about how this might be achieved?

Open the watch.
Top

Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Reply #1 on Fri 28 Nov 2008 04:53 AM (UTC)
Message
One simple way, assuming your script is not more complex than you have shown, is to do this:



characters_online.elf = characters_online.elf or {}

if characters_online.elf.warrior then
   Note ("Yay, there's at least one elf warrior online!")
end


The extra line at the start will make the table for elf, if it isn't there, and then you can test if a warrior is on.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Bobble   Canada  (76 posts)  Bio
Date Reply #2 on Fri 28 Nov 2008 01:38 PM (UTC)
Message
Excellent, this works well.

Thanks Nick. Your help is always appreciated.

Open the watch.
Top

Posted by Erendir   Germany  (47 posts)  Bio
Date Reply #3 on Sun 30 Nov 2008 11:52 PM (UTC)
Message
You can also write:


if characters_online.elf and characters_online.elf.warrior then
   Note ("Yay, there's at least one elf warrior online!")
end


it's same as


if characters_online.elf then 
   if characters_online.elf.warrior then
       Note ("Yay, there's at least one elf warrior online!")
   end
end
Top

Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Reply #4 on Mon 01 Dec 2008 03:25 AM (UTC)

Amended on Mon 01 Dec 2008 03:27 AM (UTC) by Nick Gammon

Message
Or this:


if (characters_online.elf or {} ) .warrior then
   Note ("Yay, there's at least one elf warrior online!")
end


This is slightly less efficient as it potentially creates an empty table, Erendir's idea is better in the sense that it tests for the presence of an elf entry without creating one.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Reply #5 on Mon 01 Dec 2008 03:31 AM (UTC)
Message
Another approach is to use a metatable. This would be more general as it would handle dwarves, humans, etc. as well as elves.



-- in initialization ...

characters_online = {}

setmetatable (characters_online, { __index = function () return {} end } )


-- later on ...

if characters_online.elf.warrior then
   Note ("Yay, there's at least one elf warrior online!")
end


The metatable above for characters_online returns an empty table if you try to access a non-existent index. Thus, for any race at all you will get an empty table, unless the entry already exists.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Bobble   Canada  (76 posts)  Bio
Date Reply #6 on Tue 30 Dec 2008 07:33 PM (UTC)
Message
Thanks everyone for the help on this, but I'm having a bit of a problem with metatables again.

I have a large table that lists all the various maladies I can be given in combat (it's an IRE MUD). This table lists the name of each affliction, it's specific cure, it's cure type and a bunch of other information. It looks like this:

cures = {
    { name =  "stupidity"  , cure = "goldenseal"  , type =  "herb" ,  given_by_whisper = "y"},
    { name = "anorexia"    , cure = "epidermal"   , type = "salve", given_by_whisper = "y"},
    { name = "sensitivity" , cure = "kelp"        , type = "herb" , given_by_whisper = "y"},
    { name = "asthma"      , cure = "kelp"        , type = "herb" , given_by_whisper = "n"}
}


I use this table (which is actually much larger) to create other tables that use things cure as the key with the maladies that are healed by that cure as the values.

For instance, I do something like this:
whisper_index = {}

for _, v in ipairs(cures)
  if v.given_by_whisper == "y" then
     whisper_index.type [v.type] = true
     whisper_index.cure [v.cure] = true
  end
end


Of course, doing it this way gives me an error as the "for" iteration starts, it tries to index whisper_index.type which is a nil value, because it hasn't been defined yet.

Now, a simple solution is to define
 whisper_index.names = {}


However, I'm making a lot of these indexes and I was thinking that I could use a metatable to create a situation where if a lua is asked to index a table that isn't there (such as above), it would create an empty table.

I tried this, thinking it would work, unfortunately, it didn't:
mt = {__index = function () return {} end}

whisper_index = {} 
  setmetatable(whisper_index, mt)


When the "for" chunk above goes through, all that I get is an empty whisper_index.names table.

Does anyone know where I've gone wrong here? I know there might be more efficient ways to do what I want to do, but I'm treating this as an exercise in learning how metatables and the __index function work.

Please let me know if you need further clarification or more details about what I'm trying to achieve.

Open the watch.
Top

Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Reply #7 on Tue 30 Dec 2008 07:46 PM (UTC)
Message
What the metatable does is return a value in place of the missing one. However it doesn't put it there. For example:


mt = {__index = function () return 5 end}

whisper_index = {} 

setmetatable(whisper_index, mt)

print (whisper_index ["nick"])  --> 5


This indeed prints 5 for the missing value. However it has not made whisper_index ["nick"] hold the value 5, which I think is what you are trying to do.

As far as I can see, the simplest way would be to make the subtables like this:


whisper_index = { type = {}, cure = {}, }



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Bobble   Canada  (76 posts)  Bio
Date Reply #8 on Tue 30 Dec 2008 09:00 PM (UTC)
Message
Thanks for clearing that up Nick.

Out of curiosity, is there a way to make the metatable do what I was trying to get it do? Ie. Give whisper_index["Nick"] the value of 5?

Open the watch.
Top

Posted by Nick Gammon   Australia  (23,173 posts)  Bio   Forum Administrator
Date Reply #9 on Tue 30 Dec 2008 09:17 PM (UTC)
Message
Well this seemed to do the trick:


mt = { __index = function (tbl, key) 
                   local newitem = {}
                   tbl [key] = newitem
                   return newitem  
                 end
     }

whisper_index = {} 

setmetatable(whisper_index, mt)

whisper_index.blah = 42

print (whisper_index.blah)   --> 42



The __index function gets the table and key we are trying to set, so I have used that to create an empty table, assign it to the target table, and return the new, empty, table, so that it can be used first time through. Second time through the key will exist, and __index won't have to be called.



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Bobble   Canada  (76 posts)  Bio
Date Reply #10 on Wed 31 Dec 2008 12:35 AM (UTC)
Message
Once again Nick, thanks for your help. This let me do what I was aiming to do. I very much appreciate it.

Take care!

Open the watch.
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


35,946 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.