Immutable Tables Library

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

Immutable Tables Library

Bruce Hill
Hey everybody, I just released a library that allows you to create immutable tables in Lua. The tables are stored compactly and instances are interned (but garbage collected), so the immutable tables can be used as table keys. I know there's been a lot of talk lately about "arrays with holes", and my library supports that. Here's an example of some of the functionality of the library:

Immutable = require("immutable")
Tuple = Immutable()
local foo = Tuple(1,nil,3,nil)
assert(#foo == 4)
assert(foo[1] == 1 and foo[2] == nil and foo[3] == 3 and foo[4] == nil)
local t = {[foo]="works as key"}
assert(t[Tuple(1,nil,3,nil)] == "works as key")
Point = Immutable({"x", "y"}, {name="Point", __add = function(a,b) return Point(a.x+b.x, a.y+b.y) end})
local p1 = Point(2,3)
assert(p1.x == 2 and p1.y == 3)
local p2 = p1 + Point(3,4)
assert(p2 == Point(5,7))
assert(tostring(p2) == "Point(x=5, y=7)")

Right now, the library is written for compatibility with the Lua 5.1/Lua 5.2/Lua 5.3/LuaJIT 2.0 APIs, so it's not making use of the userdata-can-have-multiple-values feature from Lua 5.4 (which I'm looking forward to because it will make the library even more efficient). Nonetheless, the library is decently efficient and I've been using and testing it in a project for a couple months now, so it should be pretty stable. I've really enjoyed using my library so far, and I hope you all like it too. Let me know if you find any bugs or find the library useful.

Source repo (with API and implementation documentation): https://bitbucket.org/spilt/lua-immutable/src
LuaRock: http://luarocks.org/modules/spill/immutable-table
or install directly via: luarocks install --server=http://luarocks.org/dev immutable-table

--
Bruce

signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

Dirk Laurie-2
2018-04-25 0:20 GMT+02:00 Bruce Hill <[hidden email]>:
> Hey everybody, I just released a library that allows you to create immutable
> tables in Lua. The tables are stored compactly and instances are interned
> (but garbage collected), so the immutable tables can be used as table keys.
> I know there's been a lot of talk lately about "arrays with holes", and my
> library supports that.

How does your library compare with pure-Lua read-only tables?

[1] https://www.lua.org/pil/13.4.5.html

Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

Hisham
On 25 April 2018 at 05:06, Dirk Laurie <[hidden email]> wrote:
> 2018-04-25 0:20 GMT+02:00 Bruce Hill <[hidden email]>:
>> Hey everybody, I just released a library that allows you to create immutable
>> tables in Lua. The tables are stored compactly and instances are interned
>> (but garbage collected), so the immutable tables can be used as table keys.
>> I know there's been a lot of talk lately about "arrays with holes", and my
>> library supports that.
>
> How does your library compare with pure-Lua read-only tables?

This works:

T[tuple("foo", "bar")] = 123
assert(T[tuple("foo", "bar")] == T[tuple("foo", "bar")])

> https://www.lua.org/pil/13.4.5.html

This doesn't:

T[readonly{"foo", "bar"}] = 123
assert(T[readonly{"foo", "bar"}] == T[readonly{"foo", "bar"}])

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

Hisham
On 25 April 2018 at 16:31, Hisham <[hidden email]> wrote:

> On 25 April 2018 at 05:06, Dirk Laurie <[hidden email]> wrote:
>> 2018-04-25 0:20 GMT+02:00 Bruce Hill <[hidden email]>:
>>> Hey everybody, I just released a library that allows you to create immutable
>>> tables in Lua. The tables are stored compactly and instances are interned
>>> (but garbage collected), so the immutable tables can be used as table keys.
>>> I know there's been a lot of talk lately about "arrays with holes", and my
>>> library supports that.
>>
>> How does your library compare with pure-Lua read-only tables?
>
> This works:
>
> T[tuple("foo", "bar")] = 123
> assert(T[tuple("foo", "bar")] == T[tuple("foo", "bar")])
>
>> https://www.lua.org/pil/13.4.5.html
>
> This doesn't:
>
> T[readonly{"foo", "bar"}] = 123
> assert(T[readonly{"foo", "bar"}] == T[readonly{"foo", "bar"}])

Or rather, this particular assert will pass because nil == nil.

Make it:

assert(T[readonly{"foo", "bar"}] == 123)

> -- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

Bruce Hill
On Apr 25, 2018, at 12:32 PM, Hisham <[hidden email]> wrote:

>
> On 25 April 2018 at 16:31, Hisham <[hidden email]> wrote:
>> On 25 April 2018 at 05:06, Dirk Laurie <[hidden email]> wrote:
>>>
>>> How does your library compare with pure-Lua read-only tables?
>>
>> This works:
>>
>> T[tuple("foo", "bar")] = 123
>> assert(T[tuple("foo", "bar")] == T[tuple("foo", "bar")])
>>
>>> https://www.lua.org/pil/13.4.5.html
>>
>> This doesn't:
>>
>> T[readonly{"foo", "bar"}] = 123
>> assert(T[readonly{"foo", "bar"}] == T[readonly{"foo", "bar"}])
>
> Or rather, this particular assert will pass because nil == nil.
>
> Make it:
>
> assert(T[readonly{"foo", "bar"}] == 123)
>
>> -- Hisham
>
Yes, that's probably the biggest difference. Some other differences though:

* Instances are interned, so only one instance of a given immutable table exists at any point in time (which is how they can be used as keys)
* Instance interning also makes equality checks work in constant time, even for deeply nested immutable tables
* next(), pairs(), ipairs() and # all have reasonable behavior with immutable tables (#Tuple(1,2,3) == 3, vs. #readOnly({1,2,3}) == 0)
* You can store nil in tables, and it will be included in # and next/pairs/ipairs iteration. e.g.

        Tuple = Immutable()
        t = Tuple("a",nil,"b",nil)
        assert(#t == 4)
        for i,v in ipairs(t) do print(i,tostring(v)) end -- prints 1 a, 2 nil, 3 b, 4 nil
        Point = Immutable({"x","y"})
        p = Point(nil,4)
        assert(#p == 2)
        for k,v in pairs(p) do print(k,tostring(v)) end -- prints x nil, y 4
        for i,v in ipairs(p) do print(i,tostring(v)) end -- prints 1 nil, 2 4

* The return value of Immutable(...) is a lot like a class, which means that it can have its own methods and class variables (provided in the optional second argument), and two different types of immutable table will never compare as equal to each other. e.g.

        Foo = Immutable({"x", "y"}, {do_thing=function(self) print("I'm a Foo") end})
        Baz = Immutable({"x", "y"}, {do_thing=function(self) print("I'm a Baz") end})
        f, b = Foo(1,2), Baz(1,2)
        assert(f != b) -- Same values, different types
        f:do_thing() -- prints "I'm a Foo"
        b:do_thing() -- prints "I'm a Baz"

* You can only set the fields you specify when the class is created (if you don't specify fields, it defaults to Tuple-like behavior). e.g.

        Foo = Immutable({"x", "y"})
        assert(not pcall(function() Foo(1,2,3,4) end)) -- Can't create a Foo with any fields besides "x" and "y"

--
Bruce

signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

dyngeccetor8
On 04/25/2018 11:55 PM, Bruce Hill wrote:
> * The return value of Immutable(...) is a lot like a class, which means that it can have its own methods and class variables (provided in the optional second argument)
[...]
> * You can only set the fields you specify when the class is created
[...]

So it's impossible to modify existing fields?

Say if implement mentioned "Point" as a class, there will be no sense
in method (Point:moveTo(x, y)) ?

-- Martin


signature.asc (853 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

Daurnimator
On 26 April 2018 at 14:41, dyngeccetor8 <[hidden email]> wrote:
>
> So it's impossible to modify existing fields?
>
> Say if implement mentioned "Point" as a class, there will be no sense
> in method (Point:moveTo(x, y)) ?

That's what immutable means.

Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

dyngeccetor8
On 04/26/2018 06:46 AM, Daurnimator wrote:
> On 26 April 2018 at 14:41, dyngeccetor8 <[hidden email]> wrote:
>>
>> So it's impossible to modify existing fields?
>>
>> Say if implement mentioned "Point" as a class, there will be no sense
>> in method (Point:moveTo(x, y)) ?
>
> That's what immutable means.

Yes. I just can't imagine useful example of class with immutable
fields. In my opinion, this implementation of immutable tables
is very good, but class analogy misguides intended usage.

-- Martin

Reply | Threaded
Open this post in threaded view
|

Re: Immutable Tables Library

Axel Kittenberger
In reply to this post by dyngeccetor8
Say if implement mentioned "Point" as a class, there will be no sense
in method (Point:moveTo(x, y)) ?

There would, kinda, point:moveTo should return a point moved by x/y. Maybe calling it "add" would be better tough.

Especially with primitives like points (or complex numbers in general), immutability makes a lot of sense in more complicated projects saving a lot of "protective copying" not knowing when a callee might change your data...