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.
# Colon syntax (keys are strings)
let person = {
name: "Alice",
age: 30,
city: "New York"
}
# Fat arrow syntax (=>)
let scores = {"Alice" => 95, "Bob" => 87, "Charlie" => 92}
# Symbol keys (distinct from string keys)
let config = { :host => "localhost", :port => 3000 }
# 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
}
};
Variable Shorthand
When a key name matches a variable name, you can omit the value. The variable's value is used automatically. Raises an error if the variable is not defined.
let name = "Alice"
let age = 30
# Shorthand - value taken from variable with same name
let person = { name:, age: }
# Equivalent to: { "name": name, "age": age }
person["name"] # "Alice"
person["age"] # 30
# Mix shorthand with regular entries
let city = "NYC"
let user = { name:, age:, "email": "[email protected]", city: }
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]"
Dot Notation
Access values using dot notation. Shorthand for bracket access. Works with nested hashes too.
let person = {name: "Alice", age: 30}
# Dot notation access
print(person.name); # "Alice"
print(person.age); # 30
# Access nested values
let user = {profile: {email: "[email protected]"}}
print(user.profile.email); # "[email protected]"
# Dot notation also allows method chaining
let data = {items: [1, 2, 3]}
print(data.items.length); # 3
# Safe navigation with &. (returns null if any part is null)
let user = {profile: null}
print(user&.profile&.email); # null (no error)
print(user&.profile&.email || "no email"); # "no email"
Modifying Hashes
Hashes are mutable - you can add, update, or remove key-value pairs.
let person = {name: "Alice", age: 30}
# Update existing key (bracket notation)
person["age"] = 31
# Add new key (bracket notation)
person["email"] = "[email protected]"
# Update or add with dot notation (both work!)
person.age = 32
person.city = "NYC"
# Remove a key (using delete method)
person.delete("email")
print(person); # {name: "Alice", age: 32, city: "NYC"}
Hash Methods
.length / .len / .size
Returns the number of key-value pairs in the hash.
let person = {name: "Alice", age: 30, city: "NYC"}
print(person.length); # 3
print(person.len); # 3
print(person.size); # 3
let empty = {}
print(empty.length); # 0
.keys
Returns an array of all keys in the hash.
let person = {name: "Alice", age: 30}
let k = person.keys
print(k); # ["name", "age"]
.values
Returns an array of all values in the hash.
let person = {name: "Alice", age: 30}
let v = person.values
print(v); # ["Alice", 30]
.entries
Returns an array of [key, value] pairs.
let person = {name: "Alice", age: 30}
let e = person.entries
print(e); # [["name", "Alice"], ["age", 30]]
.has_key(key)
Check if a key exists in the hash.
let person = {name: "Alice", age: 30}
print(person.has_key("name")); # true
print(person.has_key("email")); # false
.delete(key)
Remove a key-value pair from the hash (modifies in place).
let person = {name: "Alice", age: 30, city: "NYC"}
person.delete("age")
print(person); # {name: "Alice", city: "NYC"}
.merge(hash)
Merge another hash. Values from the other hash take precedence. Returns a new hash.
let defaults = {theme: "light", lang: "en"}
let user_prefs = {theme: "dark"}
let merged = defaults.merge(user_prefs)
print(merged); # {theme: "dark", lang: "en"}
.clear
Remove all key-value pairs from the hash.
let person = {name: "Alice", age: 30}
person.clear
print(person); # {}
print(person.len); # 0
.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(|k, v| [k, v + 5])
# {Alice: 95, Bob: 90, Charlie: 100}
# Trailing block syntax (equivalent)
let curved = scores.map |k, v| [k, v + 5] end
# filter - keep matching entries
let high = scores.filter(|k, v| v >= 90)
# {Alice: 90, Charlie: 95}
# each - iterate for side effects
scores.each |k, v| print(k + ": " + str(v)) end
# 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(|v| v * 2)
# {Alice: 180, Bob: 170}
# Trailing block syntax
let doubled = scores.transform_values |v| v * 2 end
# Transform keys
let upper_keys = scores.transform_keys(|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(|k, v| v >= 90)
# {Alice: 90, Charlie: 95}
# reject - remove where function returns true
let not_high = scores.reject(|k, v| v >= 90)
# {Bob: 85}
# Trailing block syntax
let high = scores.select |k, v| v >= 90 end
.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()
to_json
Serializes the hash to a JSON string.
let h = {name: "Alice", age: 30, active: true}
h.to_json # '{"name":"Alice","age":30,"active":true}'
let nested = {user: {name: "Bob"}, tags: [1, 2]}
nested.to_json # '{"user":{"name":"Bob"},"tags":[1,2]}'
length() / len() / size()
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]
.class / .inspect / .nil? / .is_a? / .blank? / .present?
Type introspection methods available on all types. Empty hashes are blank.
let h = {name: "Alice"}
h.class # "hash"
h.inspect # "{\"name\": \"Alice\"}"
h.nil? # false
h.is_a?("hash") # true
h.blank? # false
h.present? # true
{}.blank? # true