Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to dynamically define LUA functions

I have been playing with Lua for the past week and I ended up writing this peace of code. I find it really useful that you can dynamically create new functions that "inherit" other functions, so programmers must have a name for it. My questions are:

What is this called?

Is there a better way to do this? (cycle over a data structure and create add-on functions that "improve" existing functions)

D = {
  name = {
    value = nil,
    offset = 0,
    update = function (self)
      self.value = "Berlin"
    end,
  },
}

--Dynamic function definition
for i in pairs(D) do
  D[i].upAndWrite = function(self)
    self:update()
    print("upAndWrite was here")
  end
end

print(D.name.value)
D.name:upAndWrite()
print(D.name.value)

Result:

nil
upAndWrite was here
Berlin
like image 672
Yordan Grigorov Avatar asked Sep 12 '25 20:09

Yordan Grigorov


2 Answers

I don't think that what you're doing have special name for it, it's just on-the-fly function creation.

There are few notes regarding your code:

Proper for loop

for i in pairs(D) do
…
end

In programming, variable i is generally used for counter loops, like in

for i=1,100 do
…
end

Here, pairs returns an iterator function and the idiomatic way to use it is

for k,v in pairs(D) do
…
end

Here k is a key (like i in your code) and v is a value (use it instead of indexing table like D[k] or D[i] in your code when you need to access the corresponding value).

There's no need to create functions on the fly!

Another important thing is that you create new function on each loop iteration. While this feature is very powerful, you're not using it at all as you don't store anything using upvalues and only access data through arguments.

A better way to do it would be creating function once and assigning it to every field:

-- Define D here

do
    local function upAndWrite(self)
        self:update()
        print("upAndWrite was here")
    end

    for k,v in pairs(D) do
        v.upAndWrite = upAndWrite -- Use our function
    end
end

-- Perform tests here

What does on-the-fly function creation allow?

As mentioned above, you can utilize this very powerful mechanism of closures in certain situations. Here's a simple example:

local t = {}

for i = 1, 100 do
    -- Create function to print our value
    t[i] = function() print(i) end

    -- Create function to increment value by one
    t[-i] = function() i=i+1 end
end

t[1]()   -- Prints 1
t[20]()  -- Prints 20
t[-20]() -- Increment upvalue by one
t[20]()  -- Now it is 21!

This example demonstrates one possible usage of upvalues and the fact that many functions can share them. This can be useful in a variety of situations together with the fact that upvalues can't be changed by side code (without use of debug library) and can be trusted in general.

I hope my answer covers what you wanted to know.

Note: Also, this language is called Lua, not LUA.

like image 200
val is still with Monica Avatar answered Sep 14 '25 09:09

val is still with Monica


As a whole it doesn't have a name, no. There's lots of concepts that play into this:

  • First Class Functions aka. functions that can be assigned to variables and passed around just like numbers or strings.
  • Anonymous Functions aka. functions that are created without giving it a name explicitly. All functions in Lua are technically anonymous, but often they are assigned into a variable right after creation.
  • Metaprogramming aka. writing programs that write programs. A loop that creates functions (or methods) on an arbitrary number of objects is very simple, but I'd count it as metaprogramming.
  • Lua Tables; this may seem obvious, but consider that not all languages have a feature like this. Javascript has objects which are similar, but Ruby for example has no comparable feature.

If you're gonna use pairs, you might as well make use of both variables.

for key, object in pairs(D) do
  function object:upAndWrite(self)
    self:update()
    print("upAndWrite was here")
  end
end

Though that would create many closures, which means more work for the garbage collector, more memory usage and slower execution speed.

for key, object in pairs(D) do
  print(object.upAndWrite) -- All the functions are different
end

It's a good first stage, but after refactoring it a bit you could get this:

do
   local method = function(self) -- Create only one closure
      self:update()
      print("upAndWrite was here")
   end

   for key, object in pairs(D) do
      object.upAndWrite = method -- Use single closure many times
   end
end

Now there's only one closure that's shared among all the tables.

for key, object in pairs(D) do
  print(object.upAndWrite) -- All the functions are the same
end
like image 43
DarkWiiPlayer Avatar answered Sep 14 '25 08:09

DarkWiiPlayer