$GOBJ
References:
$GOBJ = {} syntax (associative, with names/keys)
An associative object that stores key-value pairs. Elements are accessed by their names (keys) rather than by position. This follows Grapa Object Notation (GOBJ) terminology for key-value collections.
$GOBJ vs $LIST Comparison
| Feature | $GOBJ ({}) |
$LIST ([]) |
|---|---|---|
| Syntax | {'a':1, 'b':2, 'c':3} |
[1, 2, 3] |
| Access | gobj."a" or gobj["a"] |
array[0] or array['key'] |
| Type | Associative | Positional (with key search) |
| Keys | Named keys | Numeric indices + key search |
| Order | Key-based | Position-based |
| 2D Access | .getfield(index, field) |
.getfield(index, field) |
Note: This differs from some other languages where [] is called a "list" and {} is called a "dictionary" or "object". Grapa follows traditional C terminology.
String Labels vs ID Labels in $GOBJ
Important: In $GOBJ objects, there's a crucial distinction between string labels and ID labels:
- String labels (
"x") -$STRvalues used as labels for data items - ID labels (
x) -$IDvalues used as labels for variables/functions - Variable references (
@x) resolve to the current value of variablex
Both $ID and $STR can be used as labels, but they serve different purposes and are accessed differently.
This separation prevents data items from overriding class methods:
/* Example showing the distinction */
x = "hi";
z = "by";
/* Create object with both string and ID labels */
d = {"x":"x str", x:"x id", @x:"test", @"z":"test2"};
/* Access string-labeled data item */
d."x" /* Returns: "x str" */
/* Access ID-labeled item */
d.x /* Returns: "x id" */
/* Access variable-referenced items */
d."hi" /* Returns: "test" (from @x where x="hi") */
d."by" /* Returns: "test2" (from @"z" where z="by") */
/* The object structure */
d /* Returns: {"x":"x str",x:"x id","hi":"test","by":"test2"} */
Why This Matters
Without this separation, a data item with label "len" would override the class method .len():
/* BAD: This would break .len() method */
bad_obj = {len:"some data"};
bad_obj.len(); /* Error: tries to access data instead of method */
/* GOOD: Use string labels for data items */
good_obj = {"len":"some data"};
good_obj.len(); /* Works: calls the .len() method */
good_obj."len"; /* Works: accesses the data item */
Best Practices
- For data items: Use string labels
{"key":"value"} - For variables/functions: Use ID labels
{key:"value"} - For dynamic references: Use variable references
{@var:"value"}
⚠️ IMPORTANT: Data Item Labels Must Be Quoted
In Grapa Objects ($GOBJ), there's a crucial distinction between data access and method access:
- Unquoted labels (
gobj.key): Searches for$IDor$OPin the object's class hierarchy (methods, properties) - Quoted labels (
gobj."key"): Accesses data items stored in the object
/* Example: Cryptographic key object */
crypto_keys = {
'pub': "public_key_data", /* Data item - must be quoted */
'priv': "private_key_data", /* Data item - must be quoted */
'method': 'rsa' /* Data item - must be quoted */
};
/* Correct data access */
pub_key = crypto_keys.'pub'; /* ✅ Returns "public_key_data" */
priv_key = crypto_keys.'priv'; /* ✅ Returns "private_key_data" */
/* Incorrect - would search for $ID/$OP named 'pub' */
/* pub_key = crypto_keys.pub; ❌ Would look for method/property 'pub' */
When to Use $GOBJ vs Other Data Types
Use {} (Grapa Object) When:
- Small to medium datasets (< 100-500 items typically)
- Frequent modifications (insertions, deletions, updates)
- In-memory operations only
- Simple key-value storage without complex queries
- Configuration data, user preferences, cache data
Use {}.table() (In-Memory BTree) When:
- Large datasets (> 100-500 items) where memory efficiency matters
- Range queries or ordered data access needed
- Complex queries across multiple fields
- Memory efficiency at scale is important
- Still in-memory but need better performance for large datasets
Use $file() (Persistent Storage) When:
- Data persistence is required (survives program restarts)
- Very large datasets that don't fit in memory
- Disk-based storage with BTree indexing
- Long-term data storage and retrieval
- File system integration needed
Performance Characteristics:
{}(Linked List): Very fast for small datasets due to optimized double-linked list implementation{}.table()(BTree): Slower than{}for small datasets but more memory-efficient for large datasets$file()(Persistent): Disk I/O overhead but provides persistence and handles unlimited data size
Key Insight: Don't add indexing to {} - the overhead would likely make it slower, not faster. The double-linked list implementation is already highly optimized.
| Action | Example | Result |
|---|---|---|
| Create | {'a':1, 'b':2, 'c':3} | {"a":1,"b":2,"c":3} |
| Access | {'a':1, 'b':2, 'c':3}."a"{'a':1, 'b':2, 'c':3}[1]{'a':1, 'b':2, 'c':3}[-1] | 113 |
| Assign | x = {'a':1, 'b':2, 'c':3};x."b" = "x";x["b"] = "by";x[-2] = 1234; | {"a":1,"b":"x","c":3}{"a":1,"b":"by","c":3}{"a":1,"b":1234,"c":3} |
| Append | x = {'a':1, 'b':2};x += ('c':3);x; | {"a":1, "b":2, "c":3} |
| Append | x = {'a':1, 'b':2};x ++= {'c':3,'d':4};x; | {"a":1, "b":2, "c":3, "d":4} |
| Insert | x = {'a':1, 'b':2};x += ('c':3) x[0];x; | {"c":3,"a":1,"b":2} |
| Count | {'a':1, 'b':2, 'c':3}.len() | 3 |
| Remove | x = {'a':1, 'b':2, 'c':3};x -= x[1];x; | {"a":1, "c":3} |
Advanced List Operations
Element Access Methods
Lists support both bracket notation and method-based access:
list = {'a':1, 'b':2, 'c':3};
/* Bracket notation (direct access) */
value = list["a"]; /* 1 */
value = list."a"; /* 1 - quoted for data access */
value = list[0]; /* 1 (by index) */
value = list[-1]; /* 3 (by negative index) */
/* Method-based access */
value = list.get("a"); /* 1 */
value = list.get(0); /* 1 (by index) */
value = list.get(-1); /* 3 (by negative index) */
/* Field access methods */
value = list.getfield("a"); /* 1 */
value = list.getfield(0); /* 1 (by index) */
value = list.getfield(-1); /* 3 (by negative index) */
2D Access with .getfield()
For 2D access (when accessing nested objects or specific fields), you can use .getfield() with two parameters:
/* Example: File listing object */
d = $file().ls();
/* Returns: [{"$PATH":"","$KEY":"build.py","$TYPE":"FILE","$BYTES":38707}, ...] */
/* Access specific field of a specific item */
test_bytes = d.getfield(9, "$BYTES"); /* 608 (bytes of "test" item) */
test_type = d.getfield(9, "$TYPE"); /* "DIR" (type of "test" item) */
test_key = d.getfield(9, "$KEY"); /* "test" (key of "test" item) */
/* Alternative: Chain access methods */
test_item = d.getfield(9); /* {"$PATH":"","$KEY":"test","$TYPE":"DIR","$BYTES":608} */
test_bytes = test_item.getfield("$BYTES"); /* 608 */
test_type = test_item.getfield("$TYPE"); /* "DIR" */
2D Access Methods:
- .getfield(index, field): Access specific field of item at index
- .getfield(index).getfield(field): Chain access (same result)
- .getfield(index)["field"]: Alternative bracket notation
Method Selection Guidelines:
- .get(): Use for 1D access by key or index
- .getfield(): Use for 1D access by key/index (equivalent to .get()) or 2D access with two parameters
- Bracket notation: Use for direct access when you know the structure
- Chained methods: Use .getfield().getfield() for 2D access
- Quoted access: Use gobj."key" for data items, gobj.key for methods/properties
Assignment Operations (=)
/* Direct property assignment */
list = {'a':1, 'b':2, 'c':3};
list."b" = "x"; /* {"a":1,"b":"x","c":3} - quoted for data access */
list["b"] = "by"; /* {"a":1,"b":"by","c":3} */
/* Assignment by index */
list[1] = 55; /* {"a":1,"b":55,"c":3} */
/* Assignment by negative index */
list[-2] = 1234; /* {"a":1,"b":1234,"c":3} */
/* Method-based assignment */
list.set("b", "x"); /* {"a":1,"b":"x","c":3} */
list.set(1, 55); /* {"a":1,"b":55,"c":3} */
list.set(-2, 1234); /* {"a":1,"b":1234,"c":3} */
/* Field assignment methods */
list.setfield("b", "x"); /* {"a":1,"b":"x","c":3} */
list.setfield(1, 55); /* {"a":1,"b":55,"c":3} */
list.setfield(-2, 1234); /* {"a":1,"b":1234,"c":3} */
/* Compound assignment on accessed elements */
list."b" += "dee"; /* {"a":1,"b":"bydee","c":3} - quoted for data access */
list[0] += 8; /* {"a":9,"b":"bydee","c":3} */
2D Assignment with .setfield()
For 2D assignment (when modifying nested objects or specific fields), you can use .setfield() with two parameters:
/* Example: File listing object */
d = $file().ls();
/* Returns: [{"$PATH":"","$KEY":"build.py","$TYPE":"FILE","$BYTES":38707}, ...] */
/* Modify specific field of a specific item */
d.setfield(9, "$BYTES", 1024); /* Change bytes of "test" item to 1024 */
d.setfield(9, "$TYPE", "FILE"); /* Change type of "test" item to "FILE" */
/* Alternative: Chain assignment methods */
test_item = d.getfield(9); /* Get the item */
test_item.setfield("$BYTES", 2048); /* Modify bytes field */
test_item.setfield("$TYPE", "DIR"); /* Modify type field */
2D Assignment Methods:
- .setfield(index, field, value): Set specific field of item at index
- .getfield(index).setfield(field, value): Chain access and assignment
- .getfield(index)["field"] = value: Alternative bracket notation
Addition Operations (+=)
/* Add single key-value pair */
list = {a:1, b:2};
list += (c:3); /* {a:1, b:2, c:3} */
/* Add multiple key-value pairs */
list += {d:4, e:5}; /* {a:1, b:2, c:3, d:4, e:5} */
/* Insert at specific position */
list += (f:6) list[0]; /* {f:6, a:1, b:2, c:3, d:4, e:5} */
Concatenation Operations (++=)
/* Concatenate two lists */
list1 = {a:1, b:2};
list2 = {c:3, d:4};
list1 ++= list2; /* {"a":1,"b":2,"c":3,"d":4} */
Removal Operations (-=)
list = {a:1, b:2, c:3, d:4};
/* Remove by key reference */
list -= list.c; /* {"a":1,"b":2,"d":4} */
/* Remove by key string */
list -= list["b"]; /* {"a":1,"d":4} */
/* Remove by positive index */
list -= list[0]; /* {"d":4} */
/* Remove by negative index */
list -= list[-1]; /* {} */
Unsupported Operations
list = {'a':1, 'b':2, 'c':3};
/* These do NOT work: */
list -= 2; /* No effect - value-based removal not supported */
list -= "b"; /* No effect - direct string removal not supported */
list -= list.b; /* Error - trying to remove value, not key */
Concatenation and Extension
list = {'a':1, 'b':2, 'c':3};
/* Add elements */
list += {d:4}; /* {a:1, b:2, c:3, d:4} */
/* Extend with multiple elements */
list ++= {e:5, f:6}; /* {a:1, b:2, c:3, d:4, e:5, f:6} */
/* Insert at specific position */
list ++= {g:7}, 2; /* Insert at position 2 */
/* Remove elements */
list -= {b:2}; /* Remove element with key "b" */
List Slicing
Lists support slicing operations for extracting portions of key-value pairs:
Left Slicing (.left())
list = {a:1, b:2, c:3, d:4, e:5};
list.left(3); /* {a:1, b:2, c:3} */
list.left(-2); /* {a:1, b:2, c:3} */
Right Slicing (.right())
list = {a:1, b:2, c:3, d:4, e:5};
list.right(3); /* {c:3, d:4, e:5} */
list.right(-2); /* {d:4, e:5} */
Middle Slicing (.mid())
list = {a:1, b:2, c:3, d:4, e:5};
list.mid(1, 3); /* {b:2, c:3, d:4} */
list.mid(-3, 2); /* {c:3, d:4} */
Note: Slicing methods work with any list size. For empty lists, slicing returns an empty list. Negative indices count from the end.
List Rotation
Lists support left and right rotation operations for reordering key-value pairs:
Left Rotation (.lrot())
list = {a:1, b:2, c:3, d:4};
list.lrot(); /* {b:2, c:3, d:4, a:1} */
list.lrot(2); /* {c:3, d:4, a:1, b:2} */
list.lrot(0); /* {a:1, b:2, c:3, d:4} (no change) */
Right Rotation (.rrot())
list = {a:1, b:2, c:3, d:4};
list.rrot(); /* {d:4, a:1, b:2, c:3} */
list.rrot(2); /* {c:3, d:4, a:1, b:2} */
list.rrot(0); /* {a:1, b:2, c:3, d:4} (no change) */
Note: Rotation methods preserve all key-value pairs while reordering them. For empty lists or single-element lists, rotation has no effect.
Advanced Element Finding (.findall())
The .findall() method provides enterprise-grade querying capabilities for LIST structures with support for complex patterns and logical operations.
Basic Queries
data = {name:"Alice", age:30, city:"New York"};
/* Find by property existence */
data.findall({has:{name:"age"}}) /* Returns: {data:{name:"Alice",age:30}} */
/* Find by property value */
data.findall({has:{name:"name", value:"Alice"}}) /* Returns: {data:{name:"Alice",age:30}} */
/* Find by value only */
data.findall({has:{value:"Alice"}}) /* Returns: {data:{name:"Alice",age:30}} */
Complex Nested Queries
nested = {user:{name:"Alice", age:30}, admin:{name:"Bob", age:25}};
/* Find nested objects */
nested.findall({has:{name:"user"}}) /* Returns: {"nested":{"user":{"name":"Alice","age":30},"admin":{"name":"Bob","age":25}}} */
/* Find with nested criteria */
nested.findall({has:{name:"user", has:{name:"name", value:"Alice"}}}) /* Returns: {"nested":{"user":{"name":"Alice","age":30},"admin":{"name":"Bob","age":25}}} */
/* Find with multiple nested criteria */
nested.findall({has:{name:"user", has:{name:"age", value:30}}}) /* Returns: {"nested":{"user":{"name":"Alice","age":30},"admin":{"name":"Bob","age":25}}} */
Logical Operations
data = {user:{name:"Alice", role:"admin"}, guest:{name:"Bob", role:"user"}};
/* AND logic */
data.findall({and:[{has:{name:"user"}}, {has:{name:"user", has:{name:"role", value:"admin"}}}]}) /* Returns: {"data":{"user":{"name":"Alice","role":"admin"},"guest":{"name":"Bob","role":"user"}}} */
/* OR logic */
data.findall({or:[{has:{name:"user"}}, {has:{name:"guest"}}]}) /* Returns: {"data":{"user":{"name":"Alice","role":"admin"},"guest":{"name":"Bob","role":"user"}}} */
/* NAND logic */
data.findall({nand:[{has:{name:"user"}}, {has:{name:"user", has:{name:"role", value:"user"}}}]}) /* Returns: {"data":{"user":{"name":"Alice","role":"admin"},"guest":{"name":"Bob","role":"user"}}} */
Working with Results
data = {user:{name:"Alice", age:30}, admin:{name:"Bob", age:25}};
/* Get all results */
results = data.findall({has:{name:"user"}});
results.len() /* Returns: 1 */
/* Access individual results */
first = results[0]; /* Returns: {"user":{"name":"Alice","age":30}} */
/* Access nested properties of results */
first.user.name /* Returns: "Alice" */
first.user.age /* Returns: 30 */
Error Handling and Boundary Conditions
List Access Behavior
Lists have flexible boundary behavior similar to arrays:
list = {'a':1, 'b':2, 'c':3};
/* Valid access - returns elements */
list.get("a"); /* 1 */
list.get(0); /* 1 */
list.get(-1); /* 3 */
/* Boundary behavior - APPENDS instead of error */
list.get("d"); /* Returns null (key doesn't exist) */
list.set("d", 99); /* APPENDS: {a:1, b:2, c:3, d:99} */
list.set("f", 88); /* APPENDS: {a:1, b:2, c:3, d:99, f:88} */
/* Method-based access follows same rules */
list.getfield("d"); /* Returns null (key doesn't exist) */
list.setfield("d", 77); /* APPENDS: {a:1, b:2, c:3, d:99, f:88, d:77} */
/* IMPORTANT: .set()/.setfield() only append at the end */
/* For insertion at specific positions, use += and ++= operators */
list = {'a':1, 'b':2, 'c':3};
list += (d:99) list[1]; /* INSERT at position 1: {a:1, d:99, b:2, c:3} */
list ++= {e:88, f:77} list[0]; /* INSERT multiple at position 0: {e:88, f:77, a:1, d:99, b:2, c:3} */
Key Differences from Vectors
| Behavior | $GOBJ |
$VECTOR |
|---|---|---|
| Out-of-bounds Get | Returns null |
Returns $ERR |
| Out-of-bounds Set | APPENDS element | Returns $ERR |
| Boundary Policy | Flexible (grows) | Strict (fixed size) |
| Use Case | Dynamic key-value storage | Mathematical structures |
Append vs. Error Behavior
list = {'a':1, 'b':2, 'c':3};
/* These APPEND (lists grow dynamically) */
list.set("d", 99); /* {a:1, b:2, c:3, d:99} */
list.set("f", 88); /* {a:1, b:2, c:3, d:99, f:88} */
list.setfield("g", 77); /* {a:1, b:2, c:3, d:99, f:88, g:77} */
/* These return null (key doesn't exist) */
result = list.get("z"); /* null */
result = list.getfield("z"); /* null */
/* Check for missing keys */
if (list.get("z") == null) {
/* Handle missing key */
}
Insertion vs. Appending
Important Distinction:
- .set()/.setfield(): Only append at the end
- += and ++= operators: Insert at specific positions
list = {'a':1, 'b':2, 'c':3};
/* .set() only appends at the end */
list.set("d", 99); /* {a:1, b:2, c:3, d:99} - appends at end */
/* += and ++= insert at specific positions */
list = {'a':1, 'b':2, 'c':3};
list += (d:99) list[1]; /* {a:1, d:99, b:2, c:3} - inserts at position 1 */
list ++= {e:88, f:77} list[0]; /* {e:88, f:77, a:1, d:99, b:2, c:3} - inserts at position 0 */
Error Handling Patterns
list = {'a':1, 'b':2, 'c':3};
/* Safe access with null checking */
result = list.get("missing_key");
if (result == null) {
result = "Key not found";
}
/* Safe setting (always works - appends if needed) */
list.set("new_key", 99); /* Always succeeds, appends if needed */
/* Check if key exists before access */
if (list.get("key") != null) {
value = list.get("key");
} else {
value = "Key doesn't exist";
}
Comparison with Other Data Types
/* List behavior (flexible) */
list = {'a':1, 'b':2, 'c':3};
list.set("d", 99); /* APPENDS: {a:1, b:2, c:3, d:99} */
/* Array behavior (flexible) */
arr = [1, 2, 3];
arr.set(3, 99); /* APPENDS: [1, 2, 3, 99] */
/* Vector behavior (strict) */
vec = [1, 2, 3].vector();
result = vec.set(3, 99); /* Returns $ERR - strict bounds */
Best Practices
- Use lists for dynamic key-value storage where you need flexible sizing
- Use vectors for mathematical operations where fixed dimensions matter
- Check for null returns when accessing potentially missing keys
- Use
+=and++=for insertion at specific positions - Use
.set()/.setfield()for appending at the end - Use key existence checks before accessing if you need strict behavior