Skip to content

Instantly share code, notes, and snippets.

@idevz
Forked from tylerneylon/copy.lua
Created May 9, 2018 21:27
Show Gist options
  • Save idevz/ce8a89ada6d4e56f2571d1a40dc16514 to your computer and use it in GitHub Desktop.
Save idevz/ce8a89ada6d4e56f2571d1a40dc16514 to your computer and use it in GitHub Desktop.

Revisions

  1. @tylerneylon tylerneylon created this gist Oct 14, 2014.
    89 changes: 89 additions & 0 deletions copy.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,89 @@
    -- copy.lua
    --
    -- Functions of varying complexity levels to achieve
    -- a deep copy in Lua.
    --


    -- 1. The Problem.
    --
    -- Here's an example to see why deep copies are useful.
    -- Let's say function f receives a table parameter t,
    -- and it wants to locally modify that table without
    -- affecting the caller. This code fails:
    --
    -- function f(t)
    -- t.a = 3
    -- end
    --
    -- local my_t = {a = 5}
    -- f(my_t)
    -- print(my_t.a) --> 3
    --
    -- This behavior can be hard to work with because, in
    -- general, side effects such as input modifications
    -- make it more difficult to reason about program
    -- behavior.


    -- 2. The easy solution.

    function copy1(obj)
    if type(obj) ~= 'table' then return obj end
    local res = {}
    for k, v in pairs(obj) do res[copy1(k)] = copy1(v) end
    return res
    end

    -- This functions works well for simple tables. Since
    -- it is a clear, concise function, and since I most
    -- often work with simple tables, this is my favorite
    -- version.
    --
    -- There are two aspects this does not handle:
    -- * metatables
    -- * recursive tables


    -- 3. Adding metatable support.

    function copy2(obj)
    if type(obj) ~= 'table' then return obj end
    local res = setmetatable({}, getmetatable(obj))
    for k, v in pairs(obj) do res[copy2(k)] = copy2(v) end
    return res
    end

    -- Well, that wasn't so hard.


    -- 4. Supporting recursive structures.
    --
    -- The issue here is that the following code will
    -- get stuck in an infinite loop:
    --
    -- local my_t = {}
    -- my_t.a = my_t
    -- local t_copy = copy2(my_t)
    --
    -- This happens to both copy1 and copy2, which each
    -- try to make a copy of my_t.a, which involves making
    -- a copy of my_t.a.a, which involves making a copy
    -- of my_t.a.a.a, etc. The recursive table my_t is
    -- perfectly legal, and it's possible to make a
    -- deep_copy function that can handle this by tracking
    -- which tables it has already started to copy.

    function copy3(obj, seen)
    -- Handle non-tables and previously-seen tables.
    if type(obj) ~= 'table' then return obj end
    if seen and seen[obj] then return seen[obj] end

    -- New table; mark it as seen an copy recursively.
    local s = seen or {}
    local res = setmetatable({}, getmetatable(obj))
    s[obj] = res
    for k, v in pairs(obj) do res[copy3(k, s)] = copy3(v, s) end
    return res
    end