tuple

tuple

function tuple( ... ) --> tupleTable

Description

Constructs a tuple type, i.e. an object representing an unmodifiable list of fields.

Parameters

List of tuple fields.

Return Values

tubleTable

It returns a table that allows to read the tuple fields, but it forbit the modification of the fields. When called with same arguments, the same table reference will be returned. The unused reference will be automatically garbage collected.

Code

--ZFUNC-flyweightstore-v0
local function flyweightstore() --> ( ... ) --> reference

   local NIL, NAN = {}, {}

   local meta = {
      __index = function()
         error( "Can not access interned content directly.", 2 )
      end,
      __newindex = function()
         error( "Can not change or add content to a flyweight store.", 2 )
      end,
   }

   local internstore = setmetatable( {}, { __mode = "kv" } )

   -- A map from child to parent is used to protect the internstore table's
   -- contents.
   -- In this way, they will he collected only when all the cildren are
   -- collected in turn.
   local parent = setmetatable( {}, { __mode = "k" } )

   return function( ... )
      local currentstore = internstore
      for a = 1, select( "#", ... ) do

         -- Get next intern field. Replace un-storable contents.
         local tonext = select( a, ... )
         if tonext ~= tonext then tonext = NAN end
         if tonext == nil then tonext = NIL end

         -- Get or create the correspondent sub-intern
         local substore = rawget( currentstore, tonext )
         if substore == nil then

            substore = setmetatable( {}, meta )
            parent[ substore ] = currentstore
            rawset( currentstore, tonext, substore )
         end

         currentstore = substore
      end
      return currentstore
   end
end

local tuplefact = flyweightstore()

--ZFUNC-tuple-v0
local function tuple( ... ) --> tupleTable
   local tupleTable = tuplefact( ... )
   if not getmetatable( tupleTable ).__type then -- First time initialization

      -- Store fields
      local fields = { ... }
      fields.n = select( "#", ... )

      -- Dispatch to the stored fields, and forbid modification
      setmetatable( tupleTable, {
         type = "tuple",
         __index = function( t, k ) return fields[k] end,
         __newindex = function( t, k )
            return error( "can not change tuple field", 2 )
         end,
      } )

   end
   return tupleTable
end

return tuple

Examples

local t = require "taptest"
local tuple = require "tuple"

local function diff( a, b ) return a ~= b end

-- Equality operation
t( type( tuple( 1, "a", true, 3 ) ), "table" )
t( tuple( 1, "a", true, 3 ), tuple( 1, "a", true, 3 ) )

-- Read fields
local field = tuple( 1, "a", true, 3 )
t( field.n, 4 )
t( field[ 1 ], 1 )
t( field[ 2 ], "a" )
t( field[ 3 ], true )
t( field[ 4 ], 3 )

-- Store nil and NaN
field = tuple( 1, nil, 0/0, 3 )
t( field.n, 4 )
t( field[ 1 ], 1 )
t( field[ 2 ], nil )
t( field[ 3 ], field[ 3 ], diff )
t( field[ 4 ], 3 )

-- Can not change field
local a, b = pcall( function() tuple( 1, nil, 0/0, 3 )[ 1 ] = 2 end )
t( a, false )
t( b:match( "can not change tuple field" ), "can not change tuple field" )

-- Garbage collection test

-- Check if the current lua version supports garbage collection metamethod
local has_gc_meta
setmetatable( {}, { __gc = function() has_gc_meta = true end } )
collectgarbage( "collect" )
local function skipon51( a, b )
   if has_gc_meta then return a == b end
   return true, "skipped"
end

local gccount = 0
local x = tuple( 2, nil, 0/0, 4 )
x = setmetatable( x, { __gc = function( t ) gccount = gccount+1 end } )

-- No collection if some reference is still around
collectgarbage( "collect" )
t( gccount, 0, skipon51 )

-- Automatic collection
x = nil
collectgarbage( "collect" )
t( gccount, 1, skipon51 )

t()