This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.


17.1 – Memoize Functions

A common programming technique is to trade space for time. You can speed up some functions by memoizing their results so that, later, when you call the function with the same arguments, it can reuse the result.

Imagine a generic server that receives requests containing strings with Lua code. Each time it gets a request, it runs loadstring on the string, and then calls the resulting function. However, loadstring is an expensive function and some commands to the server may be quite frequent. Instead of calling loadstring over and over each time it receives a common command like "closeconnection()", the server can memoize the results from loadstring using an auxiliary table. Before calling loadstring, the server checks in the table whether that string already has a translation. If it cannot find the string, then (and only then) the server calls loadstring and stores the result into the table. We can pack this behavior in a new function:

    local results = {}
    function mem_loadstring (s)
      if results[s] then      -- result available?
        return results[s]     -- reuse it
      else
        local res = loadstring(s)   -- compute new result
        results[s] = res            -- save for later reuse
        return res
      end
    end

The savings with this scheme can be huge. However, it may also cause unsuspected wastes. Although some commands repeat over and over, many other commands happen only once. Gradually, the table results accumulates all commands the server has ever received plus their respective codes; after enough time, this will exhaust the server's memory. A weak table provides a simple solution to this problem. If the results table has weak values, each garbage-collection cycle will remove all translations not in use at that moment (which means virtually all of them):

    local results = {}
    setmetatable(results, {__mode = "v"})  -- make values weak
    function mem_loadstring (s)
       ...    -- as before
Actually, because the indices are always strings, we can make that table fully weak, if we want:
    setmetatable(results, {__mode = "kv"})
The net result is exactly the same.

The memoize technique is also useful to ensure the uniqueness of some kind of object. For instance, assume a system that represents colors as tables, with fields red, green, and blue in some range. A naive color factory generates a new color for each new request:

    function createRGB (r, g, b)
      return {red = r, green = g, blue = b}
    end
Using the memoize technique, we can reuse the same table for the same color. To create a unique key for each color, we simply concatenate the color indices with a separator in between:
    local results = {}
    setmetatable(results, {__mode = "v"})  -- make values weak
    function createRGB (r, g, b)
      local key = r .. "-" .. g .. "-" .. b
      if results[key] then return results[key]
      else
        local newcolor = {red = r, green = g, blue = b}
        results[key] = newcolor
        return newcolor
      end
    end
An interesting consequence of this implementation is that the user can compare colors using the primitive equality operator, because two coexistent equal colors are always represented by the same table. Note that the same color may be represented by different tables at different times, because from time to time a garbage-collector cycle clears the results table. However, as long as a given color is in use, it is not removed from results. So, whenever a color survives long enough to be compared with a new one, its representation also survives long enough to be reused by the new color.