3

I know that I can store function references in tables and call them with parameters like described in the first answer here

Lua - Execute a Function Stored in a Table

But I need to store the parameters in the table for each call. How can I do that?

To explain what I want to do. I want to write a steering behaviors class. If you are calculating the steering force you can call different functions like seek(target) or pursuit(target).

I want to be able to "collect" all function calls and execute them at the end (loop over the table and execute each function with the stored parameter) or cancel everything.

Is that possible?

Community
  • 1
  • 1
burli
  • 345
  • 2
  • 12

4 Answers4

2

Another (possibly cleaner) alternative:

function xxx(s1,s2,s3)
  print(s1,s2,s3)
end

t = {}
t[#t+1] = { xxx, {'a','b','c'}}
t[#t+1] = { xxx, {'x','y','z'}}

for _,f in ipairs(t) do
  f[1](table.unpack(f[2]))
end
tonypdmtr
  • 3,037
  • 2
  • 17
  • 29
  • Is it necessary to unpack the parameter? It should be possible to use the table instead. – burli May 01 '17 at 16:14
  • Why use two tables? That wastes a cache line and creates more work for the garbage collector (and isn't cleaner/more readable than using just one table). – nobody May 01 '17 at 18:02
  • 1
    @burli You need to unpack in the general case where your called function isn't written specifically to accept all parameters as a table. – tonypdmtr May 01 '17 at 19:55
  • @nobody There are many ways one can implement such a scheme. I offered one very simple possibility that requires minimal work to use, and no extra helper functions. – tonypdmtr May 01 '17 at 20:01
2

If the maximum number of possible parameters is known, and few, you can do something simple with a closure.

local function bind(f, p1, p2, p3, p4, p5)
  return function()
    return f(p1, p2, p3, p4, p5)
  end
end

Value types will be immutable after being bound, and all parameters will be passed (even nils).

local hello = bind(print, 'hello', 123)
hello()  --> hello   123     nil     nil     nil

Of course, you could bind to reference types as well.

local coords = { x=0, y=0 }

local t1 = bind(function(t, n) t.x = t.x + n end, coords, 20)
local t2 = bind(function(t, n) t.y = t.y + n end, coords, 50)

t1(); print('transform1', coords.x, coords.y)   --> 20   0
t2(); print('transform2', coords.x, coords.y)   --> 20   50
t1(); print('transform1', coords.x, coords.y)   --> 40   50

And, you can still store everything in a table.

local t = {
  bind(function(a, b) return 'add', a + b end, 5, 5),
  bind(function(a, b) return 'sub', a - b end, 5, 5),
  bind(function(a, b) return 'mul', a * b end, 5, 5),
  bind(function(a, b) return 'div', a // b end, 5, 5),
}

for _, f in ipairs(t) do
  print(f())
end

--> add     10
--> sub     0
--> mul     25
--> div     1
Adam
  • 3,053
  • 2
  • 26
  • 29
  • I think you can remove the fixed maximum number of parameters restriction if you change your bind() function ike so: `local function bind(f, ...) local p = {...} return function() return f(table.unpack(p)) end end` – tonypdmtr May 02 '17 at 15:10
  • @tonypdmtr True, but there is already an answer that uses tables and `unpack`. This answer is intended to offer a simpler alternative. – Adam May 02 '17 at 15:14
0

If you want to store a function and some parameters in a table and later call the function with those arguments, then, well, you just store the parameters in the table together with the function and then pass those as arguments:

functions_with_parameters = {
  { 
    f = function (a, b) return a + b end, 
    args = { 1, 2 }
  },
  { 
    f = function (a, b) return a - b end, 
    args = { 100, 90 }
  }
}

for _, a in pairs(functions_with_parameters) do
  print(a.f(a.args[1], a.args[2]))
end
// 3
// 10
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
0

This is what I'd do:

-- pack one func + args into a table
function record( f, ... )
    return { func = f, n = select( '#', ... ), ... }
end

-- take a list/array/sequence of `record`ed functions & run them
function run( list )
    for _, callinfo in ipairs( list ) do
        callinfo.func( table.unpack( callinfo, 1, callinfo.n ) )
    end
end

Sample use:

todo = { }
todo[#todo+1] = record( print, "foo", "blah", nil, 23 )
todo[#todo+1] = record( print, "baz" )
run( todo )
-->  foo    blah    nil 23
-->  baz

Some obvious variations include doing a pcall in run (so errors won't abort it in the middle), or adding an extra function that takes ( list, f, ... ) and which packs the call info & appends it to the list.


If you can be certain that there's no nil in the middle of the argument list, you can simplify to…

-- pack one func + args into a table
function record( f, ... )
    return { func = f, ... }
end

-- take a list/array/sequence of `record`ed functions & run them
function run( list )
    for _, callinfo in ipairs( list ) do
        callinfo.func( table.unpack( callinfo ) )
    end
end

…but I'd strongly suggest to do this only at the end when the rest of the code is done and you know (measure!) that this is slow. (If there's a bug that introduces accidental nils into the argument list, this version will alter the argument list (by dropping elements), while the first will pass it through unchanged no matter what, which is debug-friendlier.)


If you're really short on space, you might be able to save one value's worth of space by not using a named field for the function as in…

(addendum: Except for Lua 5.2, this also seems to be a bit faster than the other two variants above – see comments below.)

-- (you can actually just do this inline)
function record( f, ... )  return { f, ... }  end

-- take a list/array/sequence of `record`ed functions & run them
function run( list )
    for _, callinfo in ipairs( list ) do
        callinfo[1]( table.unpack( callinfo, 2 ) )
    end
end

…but that depends on the number of arguments and may actually waste space! (For vanilla (PUC-Rio) Lua on x86_64, it saves 16 bytes (1 TValue) for 0, 1, 3, or 7 arguments, does nothing for 2 or 6 arguments, wastes 32 bytes for 4, 16 bytes for 5, and 112 bytes for 8 arguments (and the same power-of-two pattern keeps growing/repeating).)

nobody
  • 4,074
  • 1
  • 23
  • 33
  • Space is the smaller problem. Performance is more important than some Bytes – burli May 01 '17 at 19:06
  • Well, it puts half as much stress on the GC as the other two answers posted so far and it worked for me before (speed-wise). If your code is running, tested, and doesn't need `nil`s in arguments, switch to my second variation to save some time on creation (no need to copy the vararg list for `select`). Beyond that: Measure. And if this really turns out to be a bottleneck, post another question about optimization, with hard data, sample code, and all relevant info (fixed or max. # of args? lifetime of call info lists, current/max. runtime, …) and we can look into hacky ways to optimize further. – nobody May 01 '17 at 23:41
  • @nobody No offense, but out of pure curiosity for your claims I did measure, repeatedly, with the exact same function call and parameters for all tests, and my results were: 31.246 sec (my simplistic version) vs 39.405 sec (your first version) vs 34.522 sec (your second faster version) over 10 million iterations. Although the actual numbers will most likely differ from PC to PC, the relative differences are big enough to show which implementations have a speed advantage over others. (If you want to try it yourself I put all files here: https://www.dropbox.com/s/igsfy8kax94kr0r/test.zip?dl=0) – tonypdmtr May 02 '17 at 14:35
  • @tonypdmtr (1/2) You compared my abstracted version _with_ call overhead to your manually inlined version (_without_ that overhead). With no real workload that's a significant portion – so that's not surprising. Extended benchmark is at https://gitlab.com/__nobody/2017-05-03-microbenchmark (see (log)plot.png). Note that I added a faster version after observing the first results, which was trivial with the properly abstracted version (change 2 lines), while the manually inlined version would have been painful to change in real code (manually hunt through source / can't really search for that). – nobody May 03 '17 at 04:09
  • @tonypdmtr (2/2) (The faster version is actually my variation #3.) Further, if you look at the "clean" (`*_fun`) versions, my `n_c_fun` is slightly faster in 5.3 with the "nop" workload than `t_fun` (your version as abstracted by me), but with the (slightly more realistic) "state" workload, yours is faster. The behavior of the real code will again be different, and this microbenchmark doesn't tell you _anything_ about how it will behave. I re-iterate my point from above: measure _the real code_, then optimize. Premature optimization is the root of all evil, as we all know. :-) – nobody May 03 '17 at 04:14