Skip to main content
Version: Next

Hash

Hash keys are not restricted to strings. Any hashable value works, and a single hash may mix key types freely: STRING, INTEGER, FLOAT, BOOLEAN, ARRAY and HASH.

h = {"a": 1, 2: true, 3.5: "float", true: "bool", [1, 2]: "array"}

puts(h["a"]) // 1
puts(h[2]) // true
puts(h[3.5]) // "float"
puts(h[true]) // "bool"
puts(h[[1, 2]]) // "array"

NIL and functions are not hashable and are rejected as keys, with unusable as hash key: NIL and expected index to be hashable respectively.

Reading a key that is not present returns nil.

Calling a value stored in a hash​

👉 Introduced in 0.24

A value under a name can be read with a dot, and a callable value under a name can be called with one:

h = {"double": def(x) return x * 2 end}

puts(h.double(21)) // 42
puts(h["double"](21)) // the same thing

That makes a hash of functions read like the object it already is. The functions close over the locals of whatever built the hash, so the state is private and each one is independent:

def new_account(owner, balance)
return {
"owner": owner,
"deposit": def(n) balance = balance + n return balance end,
"describe": def() return owner + ": " + balance.to_s() end
}
end

a = new_account("robert", 100)
b = new_account("someone", 0)

puts(a.deposit(50)) // 150
puts(a.describe()) // "robert: 150"
puts(b.describe()) // "someone: 0"
puts(a.owner) // "robert" -- plain data, read the same way

A real hash method always wins, so a hash of data cannot take over size or keys. The stored value is still reachable by index:

h = {"size": def() return 99 end}

puts(h.size()) // 1 -- the hash method
puts(h["size"]()) // 99 -- the stored function

A name holding something that cannot be called says so, and a name that is not there at all reports a missing method as before:

h = {"n": 1}

h.n() // ERROR: `n` is not callable for HASH, it is INTEGER
h.other() // ERROR: undefined method `.other()` for HASH

This is not a class. The hash is still a HASH, so type() says HASH, methods() lists the hash's own methods rather than the stored ones, and there is no self or inheritance. What it adds is the call syntax for a pattern the language could already express.

people = [{"name": "Anna", "age": 24}, {"name": "Bob", "age": 99}];

// reassign of values
h = {"a": 1, 2: true}
puts(h["a"])
puts(h[2])
h["a"] = 3
h["b"] = "moo"
puts(h["a"])
puts(h["b"])
puts(h[2])h = {"a": 1, 2: true}
puts(h["a"])
puts(h[2])
h["a"] = 3
h["b"] = "moo"

// should output
1
true
3
"moo"
true

Literal Specific Methods​

clear()​

Returns HASH

Removes every entry and returns the hash, so calls can be chained.

h = {"a": 1}
h.clear()
h.size()
Output
{"a": 1}
{}
0

compact()​

Returns HASH|ERROR

Returns a new hash without the entries whose value is nil. The hash itself is unchanged; use compact! to remove them in place.

h = {"a": nil}
h.compact().size()
h.size()
Output
{"a": nil}
0
1

compact!()​

Returns HASH|ERROR

Removes the entries whose value is nil in place and returns the hash, so calls can be chained.

h = {"a": nil}
h.compact!().size()
h.size()
Output
{"a": nil}
0
0

delete(HASHABLE)​

Returns ANY

Removes the entry for a key and returns its value, or nil when the key was not there, so a removal can be told from a miss.

h = {"a": 1}
h.delete("a")
h.size()
h.delete("a")
Output
{"a": 1}
1
0
nil

each(CALLABLE)​

Returns HASH|ERROR

Calls the callback once per entry with the key and the value, and returns the hash so calls can be chained. The order the entries arrive in is not defined and differs between runs, the same caveat keys carries -- use it for a side effect per entry, not to build something ordered.

h = {"a": 1}
h.each(def(key, value) puts(key + "=" + value.to_s()) end)
Output
{"a": 1}
a=1
{"a": 1}

empty?()​

Returns BOOLEAN

Returns true when the hash has no entries.

{}.empty?()
{"a": 1}.empty?()
Output
true
false

fetch(HASHABLE, [ANY])​

Returns ANY

Returns the value for a key. Without a fallback a missing key is an error, which is the difference from get, where a fallback is required. The key has to be HASHABLE; the fallback can be anything.

h = {"a": 1}
h.fetch("a")
h.fetch("z", 0)
Output
{"a": 1}
1
0

get(HASHABLE, ANY)​

Returns ANY

Returns the value stored under the given key, or the given default when there is no such entry. The key has to be HASHABLE; the default can be anything.

{"a": "1", "b": "2"}.get("a", 10)
{"a": "1", "b": "2"}.get("c", 10)
Output
"1"
10

include?(HASHABLE)​

Returns BOOLEAN

Returns true when the hash has an entry under the given key. The argument has to be usable as a key, which is what HASHABLE in the signature means: a STRING, INTEGER, FLOAT, BOOLEAN, ARRAY or HASH.

{"a": 1, 1: "b"}.include?(1)
{"a": 1, 1: "b"}.include?("c")
Output
true
false

invert()​

Returns HASH|ERROR

Returns a new hash with keys and values swapped. Values that repeat collapse into one entry, and which one survives is not defined. A value that cannot be a key is an error.

h = {"a": 1}
h.invert().get(1, "missing")
Output
{"a": 1}
"a"

keys()​

Returns ARRAY

Returns the keys of the hash. The order is not defined and can differ between runs of the same program, so sort the result if you need it stable.

{"a": "1"}.keys()
{"a": "1", "b": "2"}.keys().sort()
Output
["a"]
["a", "b"]

merge(HASH)​

Returns HASH|ERROR

Returns a new hash with the entries of both. The argument wins where a key is in both. The hash itself is unchanged; use merge! to merge in place.

h = {"a": 1}
h.merge({"b": 2}).size()
h.size()
h.merge({"a": 9}).get("a", 0)
Output
{"a": 1}
2
1
9

merge!(HASH)​

Returns HASH|ERROR

Merges the given hash in place and returns the hash, so calls can be chained.

h = {"a": 1}
h.merge!({"b": 2}).size()
h.size()
Output
{"a": 1}
2
2

reject(CALLABLE)​

Returns HASH|ERROR

Returns a new hash of the entries the callback said no to -- the mirror of select. Use reject! to filter in place.

h = {"a": 1}
h["b"] = 2
h.reject(def(k, v) v > 1 end).size()
Output
{"a": 1}
2
1

reject!(CALLABLE)​

Returns HASH|ERROR

Drops the entries the callback said yes to and returns the hash, so calls can be chained.

h = {"a": 1}
h["b"] = 2
h.reject!(def(k, v) v > 1 end).size()
Output
{"a": 1}
2
1

select(CALLABLE)​

Returns HASH|ERROR

Returns a new hash of the entries the callback said yes to. The callback receives the key and the value. Only false and nil are no. Use select! to filter in place.

h = {"a": 1}
h["b"] = 2
h.select(def(k, v) v > 1 end).size()
h.size()
Output
{"a": 1}
2
1
2

select!(CALLABLE)​

Returns HASH|ERROR

Keeps only the entries the callback said yes to and returns the hash, so calls can be chained.

h = {"a": 1}
h["b"] = 2
h.select!(def(k, v) v > 1 end).size()
h.size()
Output
{"a": 1}
2
1
1

size()​

Returns INTEGER

Returns the number of entries.

h = {"a": 1}
h.size()
{}.size()
Output
{"a": 1}
1
0

transform_keys(CALLABLE)​

Returns HASH|ERROR

Returns a new hash with each key replaced by what the callback answered for it. A new key still has to be HASHABLE, and two keys answering the same collapse into one entry -- which of them survives is not defined. Use transform_keys! to replace them in place.

h = {"a": 1}
h.transform_keys(def(k) k.upcase() end).get("A", 0)
Output
{"a": 1}
1

transform_keys!(CALLABLE)​

Returns HASH|ERROR

Replaces each key with what the callback answered and returns the hash, so calls can be chained.

h = {"a": 1}
h.transform_keys!(def(k) k.upcase() end).get("A", 0)
Output
{"a": 1}
1

transform_values(CALLABLE)​

Returns HASH|ERROR

Returns a new hash with each value replaced by what the callback answered for it. The keys are untouched. Use transform_values! to replace them in place.

h = {"a": 1}
h.transform_values(def(v) v * 10 end).get("a", 0)
h.get("a", 0)
Output
{"a": 1}
10
1

transform_values!(CALLABLE)​

Returns HASH|ERROR

Replaces each value with what the callback answered and returns the hash, so calls can be chained.

h = {"a": 1}
h.transform_values!(def(v) v * 10 end).get("a", 0)
Output
{"a": 1}
10

values()​

Returns ARRAY

Returns the values of the hash, in the same unspecified order as keys.

{"a": "1"}.values()
{"a": "1", "b": "2"}.values().sort()
Output
["1"]
["1", "2"]

Generic Literal Methods​

is_a?(STRING)​

Returns BOOLEAN|ERROR

Returns true when the value is of the given type or belongs to the given type group, so one question covers both. The name has to be one that exists: anything else is an error rather than a false, because a typo would otherwise read as a real answer. See Types and type groups.

nil.is_a?("NIL")
"a".is_a?("HASHABLE")
nil.is_a?("HASHABLE")
true.is_a?("INTEGERABLE")
"a".is_a?("INTEGER")
Output
true
true
false
true
false

methods()​

Returns ARRAY

Returns the names of the methods specific to this literal type, not including the generic methods listed on this page. The names are sorted, so the result is the same on every run. A type with no methods of its own returns an empty array.

1.0.methods().include?("round")
true.methods()
Output
true
[]

nil?()​

Returns BOOLEAN

Returns true only for nil. Reads better than comparing against nil at the end of a chain, and every type answers it.

nil.nil?()
1.nil?()
"".nil?()
[].first().nil?()
Output
true
false
false
true

to_f()​

Returns FLOAT|NIL

Converts an object to its float representation, or nil when it cannot. A nil result is what distinguishes a failed conversion from a genuine 0.0.

1.to_f()
"1.4".to_f()
"abc".to_f()
nil.to_f()
Output
1.0
1.4
nil
nil

to_i()​

Returns INTEGER|NIL

Converts an object to its integer representation, or nil when it cannot. A nil result is what distinguishes a failed conversion from a genuine 0. For strings a 0b, 0o or 0x prefix selects binary, octal or hexadecimal and is matched case insensitively, a leading zero followed only by octal digits is octal, and anything else is decimal. The resulting integer keeps the base it was parsed with, and integers of differing bases cannot be combined directly.

true.to_i()
false.to_i()
1234.to_i()
"4".to_i()
"0".to_i()
"0125".to_i()
"0x2322".to_i()
"0b1010".to_i()
"test".to_i()
Output
1
0
1234
4
0
0o125
0x2322
0b1010
nil

to_json()​

Returns STRING|ERROR

Returns the object as json notation.

a = {"test": 1234}
a.to_json()
Output
{"test": 1234}
"{\"test\":1234}"

to_s()​

Returns STRING

Converts an object to its string representation, or the empty string when it has none. Takes no arguments; an integer renders in its own base, so use to_base first to change it.

true.to_s()
1234.to_s()
"test".to_s()
1.4.to_s()
nil.to_s()
"0b1010".to_i().to_s()
Output
"true"
"1234"
"test"
"1.4"
""
"0b1010"

type()​

Returns STRING

Returns the type of the object.

"test".type()
Output
"STRING"

type_groups()​

Returns ARRAY

Returns the type groups the value belongs to, sorted. ANY is not listed: every value belongs to it, so it would say nothing while prefixing every answer. It exists for signatures, where push(ANY) means the argument accepts anything, and is_a?("ANY") still answers true. See Types and type groups for what each group means.

1.type_groups()
nil.type_groups()
def() end.type_groups()
puts.type_groups()
Output
["COMPARABLE", "HASHABLE", "INTEGERABLE", "NUMERIC", "STRINGABLE"]
["STRINGABLE"]
["CALLABLE"]
["CALLABLE"]

wat()​

Returns NIL

Prints the type's literal-specific methods with their argument and return types, sorted by name, one per line. It returns nil rather than the listing: this exists to be read, and the REPL echoes a returned value through its escaped representation, which would put the whole thing on one line. Use methods when the names are wanted as data. A type with no methods of its own prints only the heading.

true.wat()
1.0.wat()
Output
BOOLEAN supports the following methods:
nil
FLOAT supports the following methods:
abs()
ceil([INTEGER])
divmod(FLOAT)
finite?()
floor([INTEGER])
infinite?()
nan?()
negative?()
positive?()
round([INTEGER])
truncate([INTEGER])
zero?()
nil