Tables
GameLisp's main associative data structure is the table. Tables are HashMaps
which can use
arbitrary GameLisp data for their keys.
The basic operations are similar to those for an array. You can read or write table
entries using square brackets, [tbl key]
. Assignment will create an entry if it doesn't already
exist. The has?
function will tell you whether a key is already present, and
the del!
and remove!
functions will delete an existing
entry. len
, empty?
and clear!
all work
as expected.
(let strengths (tab))
(= [strengths 'goblin] 3)
(= [strengths 'dragon] 8)
(prn (has? strengths 'goblin)) ; prints #t
(prn (has? strengths 'kobold)) ; prints #f
(prn [strengths 'goblin]) ; prints 3
(prn [strengths 'manticore]) ; an error
(prn (len strengths)) ; prints 2
(clear! strengths)
(prn (empty? strengths)) ; prints #t
Table Construction
Recall that the syntax for table literals is #((key0 value0) (key1 value1))
.
To construct a new table dynamically, you can use the tab
macro. It receives a number of array
forms of length two, and optionally a number of forms which evaluate to tables, each prefixed
with ..
. Each array form, and each entry from each of the tables, is treated as a (key value)
pair which is inserted into the table.
(let basic (tab ('a 'b) ('c 'd)))
(let more (tab ('e 'f) ..basic))
(prn more) ; prints #((a b) (c d) (e f)), not necessarily in that order
The extend!
function receives a table as its first argument, followed by
any number of (key value)
two-element arrays. Those key-value pairs are each inserted into the
table, overwriting elements which already exist. It's typically used to copy the full contents of
one table into another, by treating the source table as an iterator:
(extend! dst-table ..src-table)
Nonexistent Elements
GameLisp is normally very strict when it comes to whether or not an element of a collection exists. If you attempt to access a nonexistent table entry (or a nonexistent global, array index, object field, class field, or function parameter), it's an error.
This is in contrast to some other scripting languages, which return nil
or undefined
for
nonexistent elements. I find that this is not a sensible default: it can cause errors to
silently propagate, making refactoring and debugging more difficult.
If you need to access an element which may or may not exist, various macros support the special
syntax (? form)
. This syntax can be used in place of a key or an index. It will cause the
operation to succeed and return #n
when an element is missing, rather than triggering an error.
(let heights (tab ('mira 165) ('paul 178)))
(prn [heights 'sara]) ; an error
(prn [heights (? 'sara)]) ; prints #n
(let ar (arr 10 20 30 40 50))
(= [ar -8] -20) ; an error
(= [ar (? -8)] -20) ; a silent no-op
(prn (remove! ar 2)) ; prints 30
(prn (remove! ar 7)) ; an error
(prn (remove! ar (? 7))) ; prints #n
(prn (global (? 'possibility))) ; prints #n
(bind-global! 'possibility 100)
(prn (global (? 'possibility))) ; prints 100
Key Equivalence
All hash tables need to enforce an equivalence relation on their keys. They use this equivalence relation to establish whether, when key B is inserted into the table, it should overwrite the entry previously created for key A.
Our hash tables can use any GameLisp data as a key, including #n
and NaN floats. The equivalence
relation is represented by the function keys-eqv?
. This function is very
similar to eq?
, with a few small changes:
-
Numbers and characters act as distinct keys, even if they're numerically equal.
65
,65.0
and\A
are all==
to one another, but they're not key-equivalent. -
For performance reasons, tables have to be compared for key-equivalence using
same?
rather thaneq?
. This means that two tables can have identical contents, but still be considered distinct when used as table keys. -
Objects and Rust data can overload
eq?
, but there's no way to overloadkeys-eqv?
.
Otherwise, table keys mostly work as you would expect. Arrays and strings are key-equivalent when they have the same contents. Other reference types are key-equivalent when they refer to the same object. Value types are equivalent when they have the same type and the same contents.