tables, and functions as keys?

Posted by Gore on Sun 08 Apr 2007 08:19 PM — 23 posts, 73,113 views.

#0
Is there anyway to use a function as a key in a table, say from your healing script idea?

such as:

afflictions = { }
afflictions = {
  {name = 'paralysis', cure = eat_bloodroot (), }
  {name = 'stupidity', cure = eat_goldenseal (), }
}


This way I could reference the name and call the appropriate function to cure?
Australia Forum Administrator #1

Quote:


afflictions = { }
afflictions = {
  {name = 'paralysis', cure = eat_bloodroot (), }
  {name = 'stupidity', cure = eat_goldenseal (), }
}



You mean a value, right? From your post, the function is on the right which is the value. The word "cure" is the key.

You are on the track here, but but using the round brackets it is called immediately. Just drop them. Also, you don't need to make the table twice.


afflictions = {
  {name = 'paralysis', cure = eat_bloodroot, }
  {name = 'stupidity', cure = eat_goldenseal, }
}


In this example you have a table "afflictions" which is a numerically keyed table, that you would have to linearly scan, and when you find a match, the sub-table will have a function which is the cure.

Unless you have a reason for the linear scan, it might be simpler to key on the affliction name, like this:



-- define functions

function eat_bloodroot ()
  print "in eat_bloodroot"
end -- function eat_bloodroot

function eat_goldenseal()
  print "in eat_goldenseal"
end -- function eat_goldenseal

-- table of afflictions

afflictions = {
  paralysis = eat_bloodroot, 
  stupidity = eat_goldenseal, 
  }

-- test it

s = "stupidity"   -- test 

f = afflictions [s] -- look up affliction

if f then  -- not-nil means we found it
  f ()  -- so call function
end -- affliction found



#2
yes, sorry, I ment value, not key. The linear scan would act as a prioritized queue, as per the post you made awhile back.

Is there any chance you could explain exactly why metatables are so important?

Thanks again
Australia Forum Administrator #3
Ah yes, I knew there was a reason. The same basic idea holds, you scan the table, and once you have the function name you can call it. The nice thing about functions in Lua is you can assign them. eg.


f = function (s) print "in f" end

f1 = f   -- assign function to f1

f1 ()    -- now call it


Quote:

Is there any chance you could explain exactly why metatables are so important?


You can certainly live without metatables, however they give you the capability to alter the behaviour of tables, to an extent.

A metatable is a secondary table which is "attached" to a main table with 'setmetatable (table, metatable)'. You can then put functions into the metatable to handle things like:

  • Key not found (eg. return a default value)
  • Attempt to add a new key (eg. fail, add somewhere else)
  • String conversion (eg. print something nicer than 'table: 00CDB5A8', when you attempt to print a table value)
  • Handle arithmetic and comparison. (eg. if a table represents a mathematical quantity, like a matrix, then you could "add" or "subtract" tables)
  • Handle concatenation (eg. concatenate two tables)
  • Specify weak keys / values
  • Allow tables to be called, as if they were a function


The Programming In Lua books gives some examples, but if you can't think of a reason, don't worry. I don't use metatables much myself. Probably the "missing key" (__index) and "new key" (__newindex) operations are the ones I use the most.
#4
Why might I need to assign a function to a different name?
Australia Forum Administrator #5
Well, as in my example above:


f = afflictions [s] -- look up affliction

if f then  -- not-nil means we found it
  f ()  -- so call function
end -- affliction found


I am looking up the key in the table, the value is the function. So I assign it to f, check if not nil, and if not nil, call the function.
#6
Still having a touch of an issue...

function afflict.shiver (n,o,wc)
  aff.shiver = true
  Note ('calling queue')
  queue ()
end

aff=
  {
-- Drink affs (cold)
  shiver=false,
  frozen=false,
  }

cures= {
  {name='frozen', heal=drink.fire,},
  {name='shiver', heal='drink.fire',},
  }

function drink_fire ()
  if bal.purg then
    Send ("drink fire")
    cure.drink = "fire"
    bal.purg = .5
    DoAfterSpecial (.5, 'reset.purg ()', 12)
  end
end

-- Basically Send ('drink fire')

function queue ()
  for _, v in ipairs (cures) do
    if aff [v.name] then
      Note (v.name) 
      if v.heal then
        Note (v.heal)
        v.heal ()
        return 
      end
    end
  end
end


Now, the errors I receive..

Run-time error
World: Laeric on Lusternia (Ur'Guard)
Immediate execution
...rogram files/mushclient/scripts/lusternia/tables.lua:102: attempt to index global 'drink' (a nil value)
stack traceback:
        ...rogram files/mushclient/scripts/lusternia/tables.lua:102: in main chunk
        [C]: in function 'dofile'
        .../program files/mushclient/scripts/lusternia/init.lua:1: in main chunk
        [C]: in function 'dofile'
        [string "Script file"]:1: in main chunk


basically I use drink. as a prefix before all of my functions that involve drinking.. drink.health, drink.whatever..

if I change it to heal='drink.fire', i get..

Error number: 0
Event:        Run-time error
Description:  ...program files/mushclient/scripts/lusternia/queue.lua:8: attempt to call field 'heal' (a string value)
stack traceback:
	...program files/mushclient/scripts/lusternia/queue.lua:8: in function 'queue'
	.../program files/mushclient/scripts/lusternia/cold.lua:16: in function <.../program files/mushclient/scripts/lusternia/cold.lua:12>
Called by:    Function/Sub: afflict.shiver called by trigger
Reason: processing trigger ""


Which I assume means I can't call a string as a function..

if I change it to heal=drink_fire, (and the appropriate function..)

I get this for output:

calling queue
queue
shiver


So what am I doing incorrectly? Heh is there anyway to maintain my healing function names as drink.(whatever potion to sip)?
USA #7
You want to store the field as a function:



-- CREATE the drink table:
local drink = {}

-- FILL in the functions:
function drink.fire ()
  if bal.purg then
    Send ("drink fire")
    cure.drink = "fire"
    bal.purg = .5
    DoAfterSpecial (.5, 'reset.purg ()', 12)
  end
end
function drink.whatever()
  Send ("drink whatever")
end

-- set up the cure table
-- NOTE the lack of quotes around the function names!
cures= {
  {name='frozen', heal=drink.fire,},
  {name='shiver', heal=drink.fire,},
  }


Now, you can do something like:
cures[1].heal()

or iterate over them, calling v.heal() as you were.
#8
Sorry I didn't put in this line

drink= { }

should it be local?
USA #9
Doesn't have to be. I did it out of habit, because it's always better to not add global variables (global to the entire program as opposed to just the file in question) unless you have to. If you make a variable 'local' at the top-level of a file, then it is global to that entire file. (More technically speaking, it is local to the chunk, which in this case is the string containing the contents of the file.)
#10
still didn't work, does it matter that the different components are split up into different files?
Australia Forum Administrator #11
What was the error message? "Didn't work" is pretty vague.

Different files doesn't matter, as long as you have the syntax right, the order of instructions right, the spelling right, etc.
#12
Very sorry, received a
Run-time error
World: Laeric on Lusternia (Ur'Guard)
Immediate execution
...ogram files/mushclient/scripts/lusternia/healing.lua:144: attempt to index global 'drink' (a nil value)
stack traceback:
        ...ogram files/mushclient/scripts/lusternia/healing.lua:144: in main chunk
        [C]: in function 'dofile'
        .../program files/mushclient/scripts/lusternia/init.lua:10: in main chunk
        [C]: in function 'dofile'
        [string "Script file"]:1: in main chunk


error, when I changed drink to local drink, I'm assuming because that made the table local to the file, as opposed to all files compiled..
USA #13
It shouldn't matter because the function pointer (drink.fire) is in the same file as the local table. However, I'm not sure how exactly you have split everything up, so it might be safer to make it non-local.

One thing I note that concerns me is that nearly every error message you have shown us is coming from a different source file.
#14
Yes, queue.lua will contain all of my different queue functions, for potion afflictions, eating/smoking afflictions, etc.

healing.lua contains all of the functions regarding actually healing, drinking potions, eating herbs, smoking, applying etc

Quote:
if I change it to heal='drink.fire', i get..


Error number: 0
Event: Run-time error
Description: ...program files/mushclient/scripts/lusternia/queue.lua:8: attempt to call field 'heal' (a string value)
stack traceback:
...program files/mushclient/scripts/lusternia/queue.lua:8: in function 'queue'
.../program files/mushclient/scripts/lusternia/cold.lua:16: in function <.../program files/mushclient/scripts/lusternia/cold.lua:12>
Called by: Function/Sub: afflict.shiver called by trigger
Reason: processing trigger ""


I'm assuming the error is in the queue.lua file, with queue (), when it attempts to call heal, but the contents of heal is a string.


Quote:
Now, the errors I receive..


Run-time error
World: Laeric on Lusternia (Ur'Guard)
Immediate execution
...rogram files/mushclient/scripts/lusternia/tables.lua:102: attempt to index global 'drink' (a nil value)
stack traceback:
...rogram files/mushclient/scripts/lusternia/tables.lua:102: in main chunk
[C]: in function 'dofile'
.../program files/mushclient/scripts/lusternia/init.lua:1: in main chunk
[C]: in function 'dofile'
[string "Script file"]:1: in main chunk


is from when I have it set as cure=drink.fire, it's saying that drink is a nil value, I think?

Edit: I'm going to write a smaller version of this in one file to try and see if I can make something that's exactly what I'm doing, but set up for test purposes instead..

Thanks for all your help
Amended on Wed 11 Apr 2007 04:35 PM by Gore
USA #15
What is the name of the file in which you are defining the drink and cure tables?

And yes, trying it first in a single file is probably a good idea, once you get it right you can split it up.
#16
all tables are defined in tables.lua

I am so incredibly puzzled, by this. I'm close to figuring out what the issue is.

function queue ()
 cnote ('wtf')
  for _, v in ipairs (curelist) do
    if aff [v.name] then
    cnote (_ .. v.name .. ' ' .. v.heal) 
      if v.heal then
        v.heal ()
        return 
      end
    end
  end
end


function searchaffs ()
Note ('queue') 
  for _, v in ipairs (curelist) do
  Note (v.name)
    if aff [v.name] then 
      if v.heal then
        Note (v.heal)
        v.heal ()
        return 
      end
    end
  end
end


Those look the same to me, and are right beside eachother in my script file.

queue () produces this:

Run-time error
World: Laeric on Lusternia (Ur'Guard)
Immediate execution
[string "Script file"]:233: attempt to concatenate field 'heal' (a function value)
stack traceback:
        [string "Script file"]:233: in function 'queue'
        [string "Command line"]:1: in main chunk


searchaffs () produces this:

queue
shiver
function: 04AC9E58
drink fire
You take a drink from an amethyst vial.
A feeling of comfortable warmth spreads over you.


curelist & aff:

aff=
  {
  illusion=false,
  prone=false,
  sleep=false,
  waking=false,
  
-- Focus affs
  paralysis=false,
  leglock=false,
  throatlock=false,
  
-- Drink affs (cold)
  shiver=true,
  frozen=false,
  }
curelist = {
 {name='shiver', heal=drink.fire,},
 {name='afflict2', heal=drink.frost,},
 }


Am I missing something? Yes it looks weird because I was fiddling with different things to test it out, but with those settings.. searchaffs () works correctly and queue () does not, are they different somehow?

EDIT: Oh boy, I'm an idiot, I didn't fully read the error message before posting... removed the .. v.heal and ..

it worked, but then I moved the tables back from my script file (the one that mushclient compiles..) to tables.lua and it doesn't work anymore. No errors, gah need to experiment more.. Any ideas thus far? this as output:
queue
frozen
shiver
Amended on Wed 11 Apr 2007 06:08 PM by Gore
USA #17
It's kind of hard to debug this because I don't know exactly where things are. It would be helpful if you showed the exact file you are running, and then the exact thing you did to generate the output, followed by the output (and if it refers to lines, indicate what lines those are). Otherwise it's sort of a guessing game with respect to what is happening where.
Australia Forum Administrator #18
Quote:

... then I moved the tables back from my script file (the one that mushclient compiles..) to tables.lua and it doesn't work anymore ...


OK, you have your script in different files? How are you combining them? Using "require"? Or "dofile"? Something else?
#19
David, should I post the files to my website, would that help?

Nick, using dofile at the moment
USA #20
Well, it's just that you post a file, and then you say you changed some things and got an error message; with this kind of debugging it's really important to see the whole picture. So sure, posting them to a website would be fine, or posting them here if they're not too long.
#21
Sorry I haven't posted anything for this, I've been slightly busy as of late, but I fiddled with it (yes, sorry to be nondescript) a bit, and I have it working properly.

Basically the issue was with the table that contained the name, and cure keys, being in the same folder as the function that contained the for each statement to reference them, I believe.

I have my script file set up like this, temporarily until I change it into a plugin.

Character Script File
|All of the character stuff
|-main.lua the file that contains all of the dofiles
 |         for the system
 |-affliction record files
 |-various script files etc
 |-all of the tables file
 |-sipping portion of the script
 |-fire.lua all functions concerning fire potion affs
 |-prone.lua all functions concerning being prone
 |-focus.lua all functions cured by focus


etc etc

the problem occurred when the cure list table was A) not in the same file as the queue function, or B) both of them were not in the character script file, or the main.lua file, but rather in the third set of files.. don't know why.

I'm not a programming major or anything, so my style of organization hasn't been taught to me by someone who knows what they are doing. If anyone has any suggestions or advice, I'd appreciate it.
Australia Forum Administrator #22
It looks OK. Loading separate files shouldn't cause problems, although the devil is in the detail, as usual.

Whatever makes it easy for you to understand and maintain is fundamentally a good idea. :)