ESC
Type to search...
S
Soli Docs

Hashes

Key-value collections (dictionaries/maps) with powerful methods for data manipulation.

Creating Hashes

Hash Literals

Create hashes using curly braces with key-value pairs.

# JSON-style syntax (colon)
let person = {
    "name": "Alice",
    "age": 30,
    "city": "New York"
};

# Ruby-style syntax (=>)
let scores = {"Alice" => 95, "Bob" => 87, "Charlie" => 92};

# Empty hash
let empty = {};

# Mixed values
let mixed = {
    "number": 42,
    "string": "hello",
    "bool": true,
    "null_val": null,
    "array": [1, 2, 3],
    "nested": {"a": 1, "b": 2}
};
Nested Hashes

Hashes can contain other hashes for complex data structures.

let user = {
    "id": 1,
    "name": "Alice",
    "profile": {
        "email": "[email protected]",
        "phone": "555-1234",
        "address": {
            "street": "123 Main St",
            "city": "NYC",
            "zip": "10001"
        }
    },
    "preferences": {
        "theme": "dark",
        "notifications": true
    }
};

Accessing Values

Bracket Notation

Access values using square brackets with the key. Returns null if key doesn't exist.

let person = {"name": "Alice", "age": 30};

# Access existing keys
print(person["name"]);   # "Alice"
print(person["age"]);    # 30

# Access non-existent key (returns null)
print(person["email"]);  # null

# Access nested values
let user = {"profile": {"email": "[email protected]"}};
print(user["profile"]["email"]);  # "[email protected]"
Modifying Hashes

Hashes are mutable - you can add, update, or remove key-value pairs.

let person = {"name": "Alice", "age": 30};

# Update existing key
person["age"] = 31;

# Add new key
person["email"] = "[email protected]";

# Remove a key (using delete function)
delete(person, "age");

print(person);  # {"name": "Alice", "email": "[email protected]"}

Built-in Functions

len(hash)

Returns the number of key-value pairs in the hash.

let person = {"name": "Alice", "age": 30, "city": "NYC"};
print(len(person));  # 3

let empty = {};
print(len(empty));   # 0
keys(hash)

Returns an array of all keys in the hash.

let person = {"name": "Alice", "age": 30};
let k = keys(person);
print(k);  # ["name", "age"]
values(hash)

Returns an array of all values in the hash.

let person = {"name": "Alice", "age": 30};
let v = values(person);
print(v);  # ["Alice", 30]
entries(hash)

Returns an array of [key, value] pairs.

let person = {"name": "Alice", "age": 30};
let e = entries(person);
print(e);  # [["name", "Alice"], ["age", 30]]
has_key(hash, key)

Check if a key exists in the hash.

let person = {"name": "Alice", "age": 30};

print(has_key(person, "name"));   # true
print(has_key(person, "email"));  # false
delete(hash, key)

Remove a key-value pair from the hash (modifies in place).

let person = {"name": "Alice", "age": 30, "city": "NYC"};
delete(person, "age");
print(person);  # {"name": "Alice", "city": "NYC"}
merge(h1, h2)

Merge two hashes. Values from the second hash take precedence.

let defaults = {"theme": "light", "lang": "en"};
let user_prefs = {"theme": "dark"};

let merged = merge(defaults, user_prefs);
print(merged);  # {"theme": "dark", "lang": "en"}
clear(hash)

Remove all key-value pairs from the hash.

let person = {"name": "Alice", "age": 30};
clear(person);
print(person);  # {}
print(len(person));  # 0

Hash Methods

.get(key, default?) / .fetch(key, default?)

Get value with optional default. .fetch() raises an error if key not found and no default provided.

let scores = {"Alice": 90, "Bob": 85};

# get with default
print(scores.get("Alice"));        # 90
print(scores.get("Eve", 0));       # 0 (default)
print(scores.get("Eve"));          # null (no default)

# fetch - raises error if not found
print(scores.fetch("Alice"));      # 90
print(scores.fetch("Eve", 0));     # 0 (default)
# scores.fetch("Eve");            # Error: key not found
.dig(*keys)

Retrieve a nested value from a hash or array using a sequence of keys/indices. Returns null if any key is not found. Works with both string keys for hashes and integer indices for arrays.

let data = {"user": {"profile": {"name": "Alice"}}};

print(data.dig("user", "profile", "name"));  # "Alice"
print(data.dig("user", "settings", "theme"));  # null (path not found)

let nested = {"items": [10, 20, {"value": 99}]};
print(nested.dig("items", 2, "value"));  # 99
print(nested.dig("items", 5));  # null (index out of bounds)
.map(def) / .filter(def) / .each(def)

Transform, filter, or iterate over hash entries. Functions receive (key, value) parameters.

let scores = {"Alice": 90, "Bob": 85, "Charlie": 95};

# map - transform entries, return new hash
let curved = scores.map(def(k, v) [k, v + 5]);
# {"Alice": 95, "Bob": 90, "Charlie": 100}

# filter - keep matching entries
let high = scores.filter(def(k, v) v >= 90);
# {"Alice": 90, "Charlie": 95}

# each - iterate for side effects
scores.each(def(k, v) print(k + ": " + str(v)));
# Output: Alice: 90
#         Bob: 85
#         Charlie: 95
.transform_values(def) / .transform_keys(def)

Transform all values or keys. Returns a new hash.

let scores = {"Alice": 90, "Bob": 85};

# Transform values
let doubled = scores.transform_values(def(v) v * 2);
# {"Alice": 180, "Bob": 170}

# Transform keys
let upper_keys = scores.transform_keys(def(k) upcase(k));
# {"ALICE": 90, "BOB": 85}
.select(def) / .reject(def)

Select or reject entries based on condition. Functions receive (key, value) parameters.

let scores = {"Alice": 90, "Bob": 85, "Charlie": 95};

# select - keep where function returns true
let high = scores.select(def(k, v) v >= 90);
# {"Alice": 90, "Charlie": 95}

# reject - remove where function returns true
let not_high = scores.reject(def(k, v) v >= 90);
# {"Bob": 85}
.slice([keys]) / .except([keys])

Get subset with specified keys or without specified keys.

let user = {
    "name": "Alice",
    "age": 30,
    "email": "[email protected]",
    "city": "NYC",
    "password": "secret123"
};

# slice - only these keys
let basic = user.slice(["name", "email"]);
# {"name": "Alice", "email": "[email protected]"}

# except - exclude these keys (good for removing sensitive data)
let safe = user.except(["password"]);
# {"name": "Alice", "age": 30, "email": "[email protected]", "city": "NYC"}
.invert()

Swap keys and values. Returns a new hash.

let scores = {"Alice": 90, "Bob": 85};
let inverted = scores.invert();
# {90: "Alice", 85: "Bob"}

# Note: If values are not unique, later entries overwrite earlier ones
let dupes = {"a": 1, "b": 1};
print(dupes.invert());  # {1: "b"}
.compact()

Remove entries with null values.

let user = {
    "name": "Alice",
    "age": null,
    "email": "[email protected]",
    "phone": null
};
let cleaned = user.compact();
# {"name": "Alice", "email": "[email protected]"}
.dig(key, ...)

Navigate nested hashes safely. Returns null if any key is not found (no error raised).

let data = {
    "user": {
        "profile": {
            "name": "Alice",
            "address": {"city": "NYC"}
        }
    }
};

# Safe navigation with dig
print(data.dig("user", "profile", "name"));           # "Alice"
print(data.dig("user", "profile", "address", "city")); # "NYC"
print(data.dig("user", "nonexistent", "key"));        # null (safe, no error)

# Compare to direct access which would error:
# data["user"]["nonexistent"]["key"]  # Error!

Common Patterns

Counting Occurrences
let words = ["apple", "banana", "apple", "cherry", "banana", "apple"];

# Count occurrences
let counts = {};
words.each(def(word)
    let current = counts.get(word, 0);
    counts[word] = current + 1;
end);

print(counts);  # {"apple": 3, "banana": 2, "cherry": 1}
Grouping Data
let people = [
    {"name": "Alice", "department": "Engineering"},
    {"name": "Bob", "department": "Sales"},
    {"name": "Charlie", "department": "Engineering"}
];

# Group by department
let by_dept = {};
people.each(def(person)
    let dept = person["department"];
    if (!has_key(by_dept, dept))
        by_dept[dept] = [];
    end
    push(by_dept[dept], person["name"]);
end);

print(by_dept);
# {"Engineering": ["Alice", "Charlie"], "Sales": ["Bob"]}
Configuration Objects
# Default configuration
let defaults = {
    "host": "localhost",
    "port": 3000,
    "debug": false,
    "timeout": 30
};

# User-provided config (partial)
let user_config = {"port": 8080, "debug": true};

# Merge with defaults
let config = merge(defaults, user_config);
# {"host": "localhost", "port": 8080, "debug": true, "timeout": 30}

# Safe access with defaults
let timeout = config.get("timeout", 60);
let retries = config.get("retries", 3);  # Uses default 3

Hash Class Methods

Hash literals are automatically wrapped in a Hash class instance that provides methods for manipulation and transformation. Each hash value has access to these methods via dot notation.

to_string()

Returns a formatted string representation of the hash. Called automatically in REPL.

let h = {"name": "Alice", "age": 30};
# In REPL, displays: {"name" => "Alice", "age" => 30}
# Equivalent to: h.to_string()
length()

Returns the number of key-value pairs in the hash.

let h = Hash.new();
h.set("a", 1);
h.set("b", 2);
h.length();  # 2
get(key) / set(key, value)

Gets or sets values by key using method calls.

let h = Hash.new();
h.set("name", "Alice");
h.get("name");           # "Alice"
h.set("age", 30);
h.get("age");            # 30
has_key(key)

Returns true if the hash contains the specified key.

let h = Hash.new();
h.set("key", "value");
h.has_key("key");    # true
h.has_key("missing"); # false
keys() / values()

Returns arrays of all keys or all values in the hash.

let h = Hash.new();
h.set("a", 1);
h.set("b", 2);
let k = h.keys();     # ["a", "b"]
let v = h.values();   # [1, 2]
Hash.new()

Creates a new empty Hash instance.

let h = Hash.new();
h.length();  # 0