Skip to content

Instantly share code, notes, and snippets.

@44100hertz
Last active June 9, 2025 18:37
Show Gist options
  • Save 44100hertz/c2b49452b198b17d2dd34799200957ea to your computer and use it in GitHub Desktop.
Save 44100hertz/c2b49452b198b17d2dd34799200957ea to your computer and use it in GitHub Desktop.
Immutable table library for Lua5.1 / LuaJIT
--------------- pairs/ipairs polyfill
-- modified from:
-- https://gist.github.com/creationix/b56de2dde4021673f24242258efdcc80
do
local function mm_exists(name, trig)
local triggered = false
trig(setmetatable({}, {[name] = function () triggered = true end}))
return triggered
end
if not mm_exists("__pairs", pairs) then
local next = pairs({})
function _G.pairs(t)
local mt = getmetatable(t)
if mt and mt.__pairs then
return mt.__pairs(t)
else
return next, t, nil
end
end
end
if not mm_exists("__ipairs", ipairs) then
local iter = ipairs({})
function _G.ipairs(t)
local mt = getmetatable(t)
if mt and mt.__ipairs then
return mt.__ipairs(t)
else
return iter, t, 0
end
end
end
end
------------ immutable library
local origTable_symbol = {}
local immutable = {}
local next = pairs({})
local function immutNext(t, k)
local kn, v = next(rawget(t, origTable_symbol), k)
return kn, immutable.freeze(v)
end
local iter = ipairs({})
local function immutIter(t, k)
local kn, v = iter(rawget(t, origTable_symbol), k)
return kn, immutable.freeze(v)
end
local proxy_mt = {
__index = function (t, k)
local v = rawget(t, origTable_symbol)[k]
return immutable.freeze(v)
end,
__newindex = function (_, k, v)
error("Attempt to set field " .. tostring(k) .. " to value " .. tostring(v))
end,
__pairs = function (immut)
return immutNext, immut, nil
end,
__ipairs = function (immut)
return immutIter, immut, 0
end,
}
function immutable.freeze(t)
if type(t) == "table" then
return setmetatable({[origTable_symbol] = t}, proxy_mt)
end
return t
end
---@param t table
---@return table
function immutable.copyToMutable(t, seen)
seen = seen or {}
local copy = {}
local orig = rawget(t, origTable_symbol) or t
for k,v in pairs(orig) do
if type(v) == "table" then
if seen[v] then
-- TODO: can probably fix this...
error("Cyclical table, cannot trivially copy")
end
copy[k] = immutable.copyToMutable(v, seen)
seen[v] = true
else
copy[k] = v
end
end
return copy
end
---@param t table
---@param fn fun(t: table, ...): table
---@return table
function immutable.mutateWith(t, fn, ...)
local mut = immutable.copyToMutable(t)
return immutable.freeze(fn(mut, ...))
end
---@param im table
---@param ass table
function immutable.assign(im, ass)
local mut = {}
-- no deep copy needed, so this is pretty fast :)
for k,v in pairs(im) do mut[k] = v end
for k,v in pairs(ass) do mut[k] = v end
return immutable.freeze(mut)
end
return immutable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment