Comparing a function to another

Posted by Ksajal on Sun 25 Apr 2010 08:56 PM — 18 posts, 71,254 views.

#0
I am trying to find a way to queue function to be executed at the prompt. So far, it looks like this:

function prompt:queue (f)

self ["queued"] [#self ["queued"]+1] = f

end --function

I use it like this:

prompt:queue (function () affs:add ("affliction")end)

I execute the function stored like this:

for k, func in ipairs (prompt ["queued"])do
func ()
end --for

I need to find a way to check if I have already queued a function. The only way that I can think of is supplying an extra argument to the queue method, an unique id for the function queued so I can compare it with other queued functions.

Any other ideas? This seems rather difficult, as I need to remember all the function ids that I have supplied each time I use the queue for the same function.
USA #1
So basically you have a prompt "service", and functions can be "registered" to be executed on each prompt? And you also want to be able to tell if a function has been queued. Well, it's not too hard. :) Here's an example of what I might do (and have done before):

local queue = {}

function Register(func)
  if queue[func] then       -- check if it's in the queue
    return nil, "Already queued"
  end
              -- position, callback
  local entry = {#queue+1, func}
  table.insert(queue, entry) -- adds to the end of the queue
  queue[func] = entry        -- marks that it's in the queue
end

function Unregister(func)
  if not queue[func] then
    return
  end
  local entry = queue[func]     -- get the entry back
  table.remove(queue, entry[1]) -- get the position and dequeue it
  queue[func] = nil             -- mark that it's not in the queue
end

function DoStuff()
  local next_entry = table.remove(queue, 1) -- get from the front
  local func = next_entry[2]
  
  -- do stuff!
  
  -- make sure to update the positions of the rest of the entries
  for _,entry in ipairs(queue) do
    -- shift the position marker
    entry[1] = entry[1] - 1
  end
end



It looks a little complex, but it's not. A queue like this would look something like this in memory...


queue = {
  [1] = {1, function_foo}, -- entry 1
  [2] = {2, function_bar}, -- entry 2
  [3] = {3, function_baz}, -- entry 3
  
  [function_foo] = {1, function_foo} -- same table as entry 1
  [function_bar] = {2, function_bar} -- same table as entry 2
  [function_baz] = {3, function_baz} -- same table as entry 3
}


Every time you take an item off the queue, you make sure that the rest of the entries' positions are updated, so you can basically just use table.remove(queue, queue[function_foo][1]) to remove the entry wherever it is now.



This is how you say you're using it now:

prompt:queue (function ()
  affs:add ("affliction")
end)


You create the function and pass it to the queue in one go. But this gives you no way to refer to the function again, as you said. You'd want to do this instead:

function_foo = function ()
  affs:add ("affliction")
end

prompt:queue(function_foo)


That lets you use some "prompt:unqueue" function later, just passing in the original function:

prompt:unqueue(function_foo)



Also, since you seem to be calling all of the queued functions at once, you probably don't need to update the entries' positions in the "DoStuff" function, because the queue will be empty afterwards anyways. It's just a general example of how to do a listener queue. :)
#2
I like the code.

But this means I will still have to create an unique name for each function I am registering.

And you forgot to delete the variable containing the function from the memory when you unregister the function.

EDIT:
I think I'm gonna use it like this, in case an unique name for each function is the only way:

function prompt:queue (f, id)

for k, v in ipairs (self ["queued"] do
if v==id then
return
end --if
end --for

self ["queued"] [#self ["queued']] = id
self ["ids"] [id] = f

end --function

I only need to use it in a for loop:

for k, unique_id in ipairs (prompt ["queued"]) do
self ["ids"] [unique_id] ()
end --for

I'm only storing the function in one place.

Thank you for the help!
Amended on Sun 25 Apr 2010 10:09 PM by Ksajal
USA #3
Ksajal said:
But this means I will still have to create an unique name for each function I am registering.

If by "name" you mean variable, then yes. If you're giving away a function with no way to refer to it later, there's no reason to expect to be able to deregister it.

Ksajal said:
And you forgot to delete the variable containing the function from the memory when you unregister the function.


I don't see what you mean, sorry. I table.remove and set to nil in Deregister.

Oh, I see. In DoStuff, for the one being used.


Your string ID works, certainly, and I'm glad you have a solution. Different functions will never clash, though, while anyone can pass in an already-used string ID. ;)
Australia Forum Administrator #4
Ksajal said:

But this means I will still have to create an unique name for each function I am registering.


What is the problem exactly? You can compare functions directly.


print (print == print) --> true
print (print == tostring)  --> false

#5
Twisol said:

Your string ID works, certainly, and I'm glad you have a solution. Different functions will never clash, though, while anyone can pass in an already-used string ID. ;)

I'm usually calling prompt:queue from other functions, like this:

function affs:add_queue (name, val)
prompt:queue (function () affs:add (name, val)end, "affs_add_"..name)
end --function

Or if I need to do some very special stuff:

prompt:queue (function () some stuff end, "user_"..tostring (os.time ()))

They won't ever clash, me thinks.

Nick Gammon said:

What is the problem exactly? You can compare functions directly.

I've tried in my case, it doesn't work

one = function () Note ("") end
two = function () Note ("") end
Note (one == two)-->false

Australia Forum Administrator #6
Ksajal said:


Nick Gammon said:

What is the problem exactly? You can compare functions directly.

I've tried in my case, it doesn't work

one = function () Note ("") end
two = function () Note ("") end
Note (one == two)-->false





They are two functions that happen to do the same thing.


one = function () Note ("") end
two = one
Note (one == two) --> true


In your case, stop inlining the functions if you can. eg. instead of:


prompt:queue (function () affs:add ("affliction")end)


do:



-- define function once
function add_affliction () 
  affs:add ("affliction")
end

-- queue it if required
if something then
  prompt:queue (add_affliction)
end -- if


By only defining functions once, you now can just test if they are the same.
#7
I think I understand what you mean, but I have hundreds of afflictions, hundreds of cures, hundreds of defenses, and so on, declaring a function for each is just too much.

I've tested my unique ids table solution, it works exactly as it should so far, I'm gonna stick with it.
USA #8
Ksajal said:
I've tried in my case, it doesn't work

one = function () Note ("") end
two = function () Note ("") end
Note (one == two)-->false


Well, of course. They're two separate instances of a function. In other languages, you wouldn't expect for "new Object() == new Object()".

If you want a really messed up way to do it you can do this. Nick's going to kill me just for suggesting such an outlandish approach:

string.dump(function() end) == string.dump(function() end)


I can't test it right now - posting from my phone - but that should work for most simple cases. I can't guarantee it will if you start throwing in upvalues and modifed environments (setfenv), but it would be interesting to experiment with.
Amended on Sun 25 Apr 2010 11:32 PM by Twisol
USA #9
Ksajal said:

I think I understand what you mean, but I have hundreds of afflictions, hundreds of cures, hundreds of defenses, and so on, declaring a function for each is just too much.

I've tested my unique ids table solution, it works exactly as it should so far, I'm gonna stick with it.


You're not the first to write a curing system, and others have dealt with that problem already, more or less. Store the functions in your own tables, so they're not loose. Look at ACS2, a somewhat outdated system for Achaea, to see how they've done some of this. I don't recommend it for style or best practices, but it does work...
Australia Forum Administrator #10
Twisol said:

If you want a really messed up way to do it you can do this. Nick's going to kill me just for suggesting such an outlandish approach:

string.dump(function() end) == string.dump(function() end)




Apart from the aesthetic considerations, it just doesn't work:


s1 = string.dump (function () print "hi there" end)
s2 = string.dump (function () print "hi there" end)
 
print (s1 == s2)  --> false


s1 = string.dump (function () end)
s2 = string.dump (function () end)

print (s1 == s2)  --> false


Ksajal said:

I think I understand what you mean, but I have hundreds of afflictions, hundreds of cures, hundreds of defenses, and so on, declaring a function for each is just too much.


But you have hundreds of something, right? Because you are testing if one is equal to the other?

Generally when you seem to have hit a limitation of the language you need to revisit what you are doing. For example, if you are processing afflictions, and you want to queue up a cure, do a simple keyed lookup (or sequential lookup) of your queue of things you are planning to cure, to see if it is there already. But not by trying to compare functions per se. Stick to affliction names or cure names, or something more high level.
#11
Nick Gammon said:

Generally when you seem to have hit a limitation of the language you need to revisit what you are doing. For example, if you are processing afflictions, and you want to queue up a cure, do a simple keyed lookup (or sequential lookup) of your queue of things you are planning to cure, to see if it is there already. But not by trying to compare functions per se. Stick to affliction names or cure names, or something more high level.

This is what I am doing:

-I have a vector with all the afflictions, in the order I want to cure them, from the most dangerous to the least dangerous.
-I have a module affs which adds the affliction when I get the affliction message: affs:add (affliction) (the new affliction is stored in the the affs ["current"] table).

The line for an affliction can be an illusion (another player used a skill to make you see the line). And if it is an illusion, there is a possibility that you detect the illusion before the prompt. You detect it based on a % chance (there is a skill), or by checking against different things (like if the illusion was supposed to prone you, you can check for prone at the next prompt-the prompt shows without a doubt when you are prone-, and a few other means).

Sometimes you can be certain that an affliction line is not an illusion (for that I use affs:add), and other times you cannot (I use the queue for that).

I am not using the queue only for afflictions and I sometimes need the queue to execute a set of functions that are only used with that trigger, like this (just an example):

prompt:queue (function () EnableTrigger ("name") affs:add ("affliction") Note ("AFFLICTION")end)

And I need to be sure I am not queued the same function twice.

When I detect an illusion, I delete certain queued function (for that I use another table entry in the prompt queue), which I previously marked for deletion when I stored them into the queue (this is the previous function, marked for deletion):

prompt:queue (function () EnableTrigger ("name") affs:add ("affliction") Note ("AFFLICTION")end, "delete_it_if_illusion")

That is a little besides the point, the problem still remains, I need an unique name for the function for both comparing with previous entries in the queue, and for storing it for removal in case of an illusion. Problem which I solved by assigning an unique id for the function queued, like this ("affs_add_paralysis" is the id):

prompt:queue (function () affs:add ("paralysis") end, "affs_add_paralysis", "delete_it_if_illusion")
Amended on Mon 26 Apr 2010 01:54 PM by Ksajal
USA #12
All of your operations appear to be data-driven, meaning that you can define the operation based on the data it operates on, and you are not forced to define it as the functional operation itself.

It's unclear to me why (or even if) you need to queue whole functions and not the data they're operating on. For example, why this:

prompt:queue (function () affs:add ("paralysis") end, "affs_add_paralysis", "delete_it_if_illusion")

and not this:
prompt:queue ("paralysis", "delete_it_if_illusion")


(I'm not sure why you need the other parameter so I'm leaving it there for now.)

If you need to have a case where the note is printed, you could do:
prompt:queue ({aff="paralysis", print_note=false, enable_trigger=false}, "delete_it_if_illusion")

and
prompt:queue ({aff="paralysis", print_note=true, enable_trigger=true}, "delete_it_if_illusion")

etc.

Now you can easily test equality of two affliction chunks. After all you are trying to store data in your queues, not whole functional operations.
#13
Example of uses:

--the function
function prompt:queue (f, unique_id, if_I_remove_it_in_case_of_illusion)

for k, v in ipairs (self ["queued"] do
if v==id then
return
end --if
end --for

self ["queued"] [#self ["queued']] = id
self ["ids"] [id] = f

if if_I_remove_it_in_case_of_illusion then
self ["check"] [id] = true
end --if

end--function

--uses
prompt:queue (function () system:cured_asleep ()end, "cured_asleep", "remove_it_if_illusion")

--or

prompt:queue (function () system:cured_asleep flags:add_check ("recklessness") ()end, "some_unique_id", true)

--or

prompt:queue (function ()
  system:cured ("slitthroat")
  if bals:get ("elixir")==0.5 and not string.find (flags:get ("applying_salve") or "nil", "health") then fst:elixir () end
  if bals:get ("purg")==0.5 then fst:purg ()end
  if bals:get ("herb")==0.5 then fst:herb ()end
  end, "another_unique_id", true)

--or

prompt:queue (function () defs:lostdef (defense) end, "defs_lostdef_defense", true)

I'm not only storing affliction names.
Amended on Mon 26 Apr 2010 02:36 PM by Ksajal
USA #14
My suggestion works with more than just names, but it does seem like your functions are complex enough to not be just data. But then the question is why exactly can't you give these functions proper names or at least values that you can directly compare?
#15
David Haley said:

My suggestion works with more than just names, but it does seem like your functions are complex enough to not be just data. But then the question is why exactly can't you give these functions proper names or at least values that you can directly compare?

Because for each module that may queue a function I have a method that does that automatically, without me directly specifying the function. I'm not a programmer, I do it just for fun, I find it difficult to find the right words (I'm not even a native English speaker, just take a look at the typo in the thread title), and my code may seem really weird at times, but that's how I imagined it working, so here is an example.

Most of the time, I queue the function affs:add (name, val) in my prompt. So instead of typing each time prompt:queue (function () affs:add ("affliction")end, "affs_add_affliction", true) I use this:

function affs:add_queue (name, val)

prompt:queue (function () affs:add (name, val)end, "affs_add_"..name, true)

end--function

affs:add_queue ("affliction")


It's just easier to type, and modify. Instead of modifying each different function, I just modify the affs:add_queue (name, val). The same reason I use affs:add (name, val) instead of affs ["current"] [aff] = val
Amended on Mon 26 Apr 2010 05:39 PM by Ksajal
USA #16
I don't really understand what forces you to do it this way or even why it's more convenient. Maybe there's something I'm not seeing based on what you have or haven't said about your problem description, but this setup just seems weird and convoluted to me.

If I'm understanding you correctly, your problem is that you want a generic way of adding functions to your prompt queue, and don't want to list out 100 functions that are all the same.

That's ok: you can still give them names or at least handles, if you create a map from name to function:


prompt_funcs = {}
names = {'affliction', 'bla', 'foo'}

for _, name in ipairs(names) do
  -- val is nil here and in your code, by the way
  local val = nil
  prompt_funcs[name] = function () affs:add (name, val) end
end


Now, for a given name, you have a unique function that adds that name.

In other words, to give functions unique handles, you don't need to spell every one out explicitly; you can generate the functions.

I suppose it's kind of ok to create an identifier as you ad things into the queue, but that's unsafe and kind of weird.

(EDIT: fixed implicitly --> explicitly)
Amended on Mon 26 Apr 2010 07:42 PM by David Haley
USA #17
David Haley said:


prompt_funcs = {}
names = {'affliction', 'bla', 'foo'}

for _, name in ipairs(names) do
  -- val is nil here and in your code, by the way
  local val = nil
  prompt_funcs[name] = function () affs:add (name, val) end
end


Now, for a given name, you have a unique function that adds that name.

In other words, to give functions unique handles, you don't need to spell every one out implicitly; you can generate the functions.


That's also the approach ACS2 (which I pointed out before) takes for generating its affliction-handler functions. :)