Immutable πŸ”’ runtime constant Lua tables

Immutable data structures are constant, unmodifiable data structures that can simply be only read. You can instantiate them only once and through the whole program this data will remain unchanged until you delete them. This opens up powerful abilities when used wisely in e.g. Data Oriented, Reactive, Distributed, Event-based, Flow-based or Functional programming, etc.

Lua does not offer immutable tables as a language feature, but it can be simulated to a fairly good degree in runtime. I think I stretched it enough to publish a module I use for making immutable tables in Lua.

I present to you β€œIMMUTABLE”:


IMMUTABLE makes any Lua table runtime immutable (read-only) where one:

  • can get defined entries
  • cannot add, remove nor change entries
  • cannot access values at undefined keys nor out of bounds elements

Usage:

Step 1. Require it:

local IMMUTABLE = require "immutable.immutable"

Step 2. Create an immutable table (3 ways):

local data_table = { 1, 2, 3 } -- regular table

-- convert any table to an immutable table:
--[[1]] local my_immutable_table = IMMUTABLE.make( data_table )

-- or simply using convenience call:
--[[2]] local my_immutable_table = IMMUTABLE( data_table )

-- or create it on the go (syntax sugar for call with table parameter):
--[[3]] local my_immutable_table = IMMUTABLE{ 1, 2, 3 }

API:

IMMUTABLE.make(original_table)

-- Function to make a table immutable, including nested tables
@param original_table [table] - table to convert to immutable table
@return [table] - original table converted to immutable table

IMMUTABLE.is_immutable(table_to_check)

-- Function to check if a given table `t` is immutable
@param table_to_check [table] - table to check
@return [bool] - true if table t is immutable, false otherwise

Known issues:

  • Keys referencing nil in the original table will be inaccesible in the immutable table.
    To prevent unidentified key access, initialize the fields with any other value.

  • Immutable tables are secured against modifications in any sort of loops, but are not secured against usage with table API (because table API functions ommit __index calls)

      table.insert(immutable{ 1, 2 }, 3, 3) -- will NOT raise an error!
      table.remove(immutable{ 1, 2 }, 2) -- will NOT raise an error!
    

    Known workaround is to override the table API functions, for example insert and remove. Example of this is provided in README.


License: MIT

Source code:


Above is a general use Lua module for everyone coding in Lua. If you wish to add IMMUTABLE as Defold dependency, use link from the below repository instead

An example of a Tic Tac Toe game I created in Defold using it:

12 Likes

I have one place where I need this (or similar) module =)
Thank you!

2 Likes

Glad it might be useful! :smiley:

In the lua-immutable repository you can find tests - I think it’s one good way to learn the possibilities of the module :wink:

If you will need more functionalities or wish to modify current, I would love to accept PR for this, especially for the known issues I don’t know how to gently overcome :smiley:

1 Like

Immutable has been upgraded to 1.1:

  • Added Defold project and allowed to include Immutable as dependency in Defold.
  • Added Lua annotations.

You can now include it in Defold as dependency using latest release, just paste:

https://github.com/paweljarosz/lua-immutable/archive/refs/tags/v1.1.zip

It also got a media banner and is now submitted to Defold Assets Portal :wink:

5 Likes

I just found this, how does Immutable stack up to it?

Fennel is a lisp-like language, that compiles to Lua.
It is possible to embed it like Lua, as it is a one file library (and executable)

This also seems to implement transducers, lazy sequences, and more.

2 Likes

This module is only a series of tricks to make runtime immutability in Lua, but if you are looking for a language with native immutability and it can transcompile to Lua, I don’t see any obstacles to try it out in Defold :grin: I am interested myself even, how does they solve it in Lua translation

1 Like

I asked for it in their Matrix. Even before getting an answer, the author of Fennal says a couple of lines above my question, that the Clojure library is the hardest one to ship in his entire project. ^^

So its probably not that easy.
Nice that we can use it from normal Lua!

1 Like

Answer: It uses metatables that override the __newindex and __index methods

And there is the underlying implemetation

2 Likes

It’s not really hard to ship if you first compile it to Lua - then it’s just a self-contained single-file Lua library. What makes it harder to ship are macros. Still possible, of course, just a bit of manual work putting all of the sources in the right place and setting the FENNEL_PATH and FENNEL_MACRO_PATH variables.

However I wouldn’t recommend using fennel-cljlib in games, as it is going to slow everything down a fair bit, because there is a lot of runtime dispatch, immutability adds up, lazy sequences are a bit expensive and implemented as closure-based-lists, and so forth:slight_smile:

itable, on the other hand, should be alright for reading, but modification is still not as fast as with pure mutable tables. Cljlib actually transforms immutable tables back to mutable underneath (called transients) and mutates them in place, then transforms back to immutable, achieving speeds comparable to using mutable tables, but with immutability guarantees.

2 Likes