itable icon indicating copy to clipboard operation
itable copied to clipboard

Immutable table manipulation library for Fennel and Lua. (mirror)

itable

Immutable tables for Lua runtime.

Installation

Clone this library to your project:

git clone https://gitlab.com/andreyorst/itable.git

Rationale

Tables in Lua are mutable by default, and the table module from the standard library almost exclusively consists of impure functions. The reason behind it is partially related to garbage collection in Lua - it wasn't optimized for cleaning lots of small objects, and could result in a big GC pauses. Lua 5.4 introduced generational garbage collector, which efficiently works with a lot of objects that can be cleaned early.

Immutable tables are much more reliable, as those can be shared without worrying that the values may change. Not mutating tables is an option, but there's no guarantees that some function won't alter a table it was passed, either by mistake, or intentionally. This library is meant as (sort of) a drop-in replacement for Lua's own table module. It provides all functions from table module that now produce immutable tables, plus addition functions for more convenient immutable table manipulation.

Design

The itable module contains all functions from table module, which produce immutable tables instead of modifying tables in place, plus a lot of additional functions for table manipulation. Any functions that produce new inner tables ensure that these inner tables are also immutable. Module table has a __call metamethod, which is used as a constructor for immutable tables:

local itable = require "itable"

local t = itable{1, 2, 3}

However, note that if the table passed has inner tables, itable constructor won't make those immutable. For that purpose use deepcopy constructor:

local t1 = itable{numbers = {1, 2, 3}, letters = {"a", "b", "c"}}
-- can still mutate t1.numbers and t1.letters
local t2 = deepcopy{numbers = {1, 2, 3}, letters = {"a", "b", "c"}}
-- can't mutate inner tables

Differences from Lua's table module

Apart from making all functions return a new immutable table, some functions were modified for convenience.

itable.concat

table.concat expects only numbers and strings in the concatenated table, which often causes runtime errors.

itable.concat invokes tostring on all elements by default, and accepts optional function to use instead of tostring, and a set of options for that function. For example:

local inspect = require "inspect" -- https://github.com/kikito/inspect.lua
local t = {1, 2, {a = 1, b = 2}}

table.concat(t, ", ")
-- invalid value (table) at index 3 in table for 'concat'
itable.concat(t, ", ")
-- 1, 2, table: 0x7c72639b80
itable.concat(t, ", ", nil, nil, inspect, {newline = " ", indent = ""})
-- 1, 2, { a = 1, b = 2 }

itable.remove

table.remove returns the value associated with the removed key.

itable.remove returns a new table as its first value, and the removed value as a second value:

local t1 = {"a", "b", "c"}
local t2, v = itable.remove(t1, 2)
-- t1 left intact {"a", "b", "c"}
-- t2 is {"a", "c"}, v is "b"
-- t2 is also immutable

In addition to the standard Lua set of functions from table module, itable provides a lot of extra functions for table manipulation. Descriptions, and examples can be found in the docs

Note on the self reference

Eager immutable tables can't contain any form of self reference, so is impossible to create a cyclic table with itable constructor. The deepcopy constructor, which produces deep immutable copies of mutable tables, will throw error on detecting a cycle.

When converting existing cyclic table with itable constructor, the resulting table will be immutable on the first level, but will contain a cycle to the original table:

local inspect = require "inspect"

local cyclic = {}
cyclic['cycle'] = cyclic

inspect(itable(cyclic))
--[[
{
  cycle = <1>{
    cycle = <table 1>
  },
  <metatable> = {}
}
]]

Development

This library is written using the Fennel programming language and compiled to Lua. The init.lua file is not meant for hand editing. For testing purposes a fennel-test library is used as a git submodule. It's not strictly required to write Fennel unless you're submitting a merge request or sending a patch - you can suggest additions using Lua, and I'll convert this to Fennel.

Documentation

The documentation is auto-generated with Fenneldoc and can be found here.

Contributing

Please do. You can report issues or feature request at project's GitLab repository. Consider reading contribution guidelines beforehand.