Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tuples, Vecs, and Dicts

Whim arrays are values. Tuples are fixed and immutable. Vecs and dicts are mutable.

Tuples

A tuple may hold a different type at each position:

$user = (7, 'Ada', true);
assert!($user[0] == 7);
assert!($user is (int, string, bool));

A one-item tuple needs a trailing comma:

$one = ('only',);
assert!($one[0] == 'only');

() is not a value. Tuples may hold 1 through 12 items. Their indexes do not change, and tuple elements reject writes.

A tuple type may end with a rest type:

function read_row((int, ...string) $row): int {
  return $row[0];
}

assert!(read_row((7, 'a', 'b')) == 7);

Vecs

A vec has dense integer keys from zero:

$names = vec['Ada', 'Grace'];
$names[1] = 'Hopper';
$names[] = 'Linus';

assert!($names == vec['Ada', 'Hopper', 'Linus']);

Indexed assignment must replace an existing position. Use $vec[] = $value to append.

The fill form evaluates its value once, then repeats it:

$zeros = vec[0; 4];
assert!($zeros == vec[0, 0, 0, 0]);

Spread a vec or tuple into a vec literal:

$middle = vec[2, 3];
assert!(vec[1, ...$middle, 4] == vec[1, 2, 3, 4]);

A dict cannot spread into a vec.

Dicts

A dict accepts bool, int, and string keys. It keeps insertion order:

$scores = dict['Ada' => 10, 'Grace' => 12];
$scores['Ada'] = 11;
$scores['Linus'] = 9;

assert!($scores['Ada'] == 11);
assert!(length!($scores) == 3);

Keys do not convert. 1, '1', and true are three different keys.

Replacing a value keeps the key’s place. Removing a key and adding it again moves it to the end.

A duplicate literal or spread key keeps its first place and its last value:

$key = 'a';
$values = dict[$key => 1, 'b' => 2, $key => 3];
assert!($values == dict['a' => 3, 'b' => 2]);

A dict spread accepts a tuple, vec, or dict. A tuple or vec contributes integer keys. A dict keeps its keys.

Reading entries

An index must exist. Reading a missing vec position, tuple position, or dict key throws OutOfBoundsError. ?? does not hide that error:

$values = dict['ready' => true];
assert!(!contains_key!($values, 'missing'));

Check with contains_key! before the read. contains! checks values:

$values = vec[10, 20];
assert!(contains_key!($values, 1));
assert!(contains!($values, 20));

length! works on every array and on strings.

Removing entries

remove!($array, $key) removes and returns one entry. On a vec, it preserves order by shifting every later item left. remove_first!($vec) is the same ordered operation at index zero. Use these forms when remaining indexes and iteration order must not change.

Both operations take time in proportion to the items after the removed one. Repeated remove_first! calls therefore take quadratic time. Use Whim\DataStructure\Deque for repeated FIFO removal.

swap_remove!($vec, $index) instead moves the last item into $index. It takes constant time on an unshared vec, but changes order. Use it for unordered work sets and pools. remove_last!($vec) also takes constant time and does not reorder the remaining items.

$values = vec['a', 'b', 'c', 'd'];
$removed = swap_remove!($values, 1);

assert!($removed == 'b');
assert!($values == vec['a', 'd', 'c']);

All vec mutations may first copy storage when another vec shares it.

Iteration

foreach walks keys and values in array order:

$seen = vec[];
foreach (dict['a' => 1, 'b' => 2] as $key => $value) {
  $seen[] = $key . $value;
}

assert!($seen == vec['a1', 'b2']);

The loop binds copies. Assigning $key or $value does not change the array. The loop also keeps the set of entries with which it began, so changing the source during the loop does not change the current walk.