lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


It was thus said that the Great Tim Hill once stated:
> 
> > 
> > There are exactly two times when the __key metamethod gets called:
> > 
> > t[ud] = v  --> t[ud.__key()] = v
> > v = t[ud]  --> v = t[ud.__key()]
> > 
> > Of note, it does NOT get called in this situation:
> > 
> > k = ud.__key()
> > assert(t[k] == v) -- no __key used here!
> > 
> > But this absolutely ought to work.
> > 
> > Yes, there's ONE extra check made when accessing a table ("does the
> > key have a __key metamethod, yes or no?") so yes it does TECHNICALLY
> > make things more expensive, but this isn't anywhere close to a "good
> > luck with performance" kind of impact.
> > 
> 
> Where is the synthetic key value stored then? It must be stored in the
> key+value slot of the table. Where is the userdata stored? In the same
> place. So now we are storing three items per table slot, not two. Seems to
> me a significant impact.

  To clarify Tim's position here:

	foo = require "foo"	-- getting away from the bigint thingy

	t = {}
	x = foo.make('alpha')
	t[x] = "booya!" -- __key method triggered on 'x'
	x = nil
	collectgarbage('collect')
	collectgarbage('collect')
	collectgarbage('collect') -- just to make sure!
	y = foo.make('alpha') -- intended to return the same __key as 'x'
	print(t[y]) -- __key method triggered on 'y'

Other things to keep in mind ... 

	t = {}
	x = foo.make('alpha')
	t[x] = "booya!"
	x = nil 
	collectgarbage('collect') -- repeat as needed

	for name,value in pairs(t) do
	  print(name)	-- Um ... what now?
	  print(type(name)) -- and what is the type?
	  print(getmetatable(name)) -- and does it have a metatable?
	  print(debug.getuservalue(name)) -- or a uservalue?
	end

	alpha = getmetatable(foo.make('alpha'):__key()
	beta  = getmetatable(foo.make('beta'):__key()

	assert(alpha ~= beta)
	assert(beta  ~= alpha)

	collectgarbate('collect') -- repeat as needed

	ALPHA = getmetatable(foo.make('alpha'):__key()
	BETA  = getmetatable(foo.make('beta'):__key()

	assert(ALPHA == alpha)
	assert(alpha == ALPHA)
	assert(BETA  == beta)
	assert(beta  == BETA)

	collectgarbage('collect') -- again, repeat as needed

	assert(ALPHA == alpha)
	assert(BETA  == beta)
	-- add other combinations as required

	t = {}
	t[true]                = "alpha"
	t[false]               = "beta"
	t[1]                   = "gamma"	-- or any integer key in the table
	t[1.5]                 = "delta"	--   "    number    "
	t['alpha']             = "epsilon"	--   "    string    " 
	t[t]                   = "zeta"		--   "    table     "
	t[print]               = "eta"		--   "    function  "
	t[coroutine.running()] = "theta"	--   "    coroutine "
	t[io.stdin]            = "iota"		--   "    userdata  " [1]

	-- ----------------------------------------------------------------
	-- In English, the symbol " can be used for 'ditto', which loosely
	-- means "the same", so for the above comments, each one should be
	-- read as "or any ______ key in the table".
	--
	-- [1] The table given is not intended at this point to have any key
	-- as a result of foo.make() in it, so the following, if I
	-- understand this correctly, should not overwrite any existing
	-- field of the given table (I'm going for a generalized __key thing
	-- here, not just a "bigint" as per the previous example).
	-- ----------------------------------------------------------------

	x    = foo.make('alpha')
	t[x] = "booya!"

> And yes, a given __key *should* return the same value each time it is
> called for a given userdata in a given state. But if it doesn’t strange
> things happen. Strange in that they vastly complicate the table contract.
> And to what end?
> 
> I’m not being obtuse here, I just don’t see the bang for the buck.

  As I present these examples, I'm just not seeing it either, given that a
module that wants the behavior of '__key' can just return interned values.

  -spc (__key is hard!  Let's make rockets!)