Expressions And Operators: Coalesce
Given the expression e1 ?? e2, if e1 is defined and not null, then the
result is e1. Otherwise, e2 is evaluated, and its value becomes the result.
There is a sequence point after the evaluation of e1.
$nully = null;
$nonnull = 'a string';
\print_r(vec[
$nully ?? 10, // 10 as $nully is `null`
$nonnull ?? 10, // 'a string' as $nonnull is `nonnull`
]);
$arr = dict['black' => 10, 'white' => null];
\print_r(vec[
$arr['black'] ?? -100, // 10 as $arr['black'] is defined and not null
$arr['white'] ?? -200, // -200 as $arr['white'] is null
$arr['green'] ?? -300, // -300 as $arr['green'] is not defined
]);
It is important to note that the right-hand side of the ?? operator will be
conditionally evaluated. If the left-hand side is defined and not null, the
right-hand side will not be evaluated.
$nonnull = 4;
// The `1 / 0` will never be evaluated and no Exception is thrown.
$nonnull ?? 1 / 0;
// The function_with_sideeffect is never invoked.
$nonnull ?? function_with_sideeffect();
?? and idx()
The ?? operator is similar to the built-in function idx(). However, an
important difference is that idx() only falls back to the specified default
value if the given key does not exist, while ?? uses the fallback value even
if a key exists but has null value. Compare these examples to the ones above:
$arr = dict['black' => 10, 'white' => null];
\print_r(vec[
idx($arr, 'black', -100), // 10
idx($arr, 'white', -200), // null
idx($arr, 'green', -300), // -300
idx($arr, 'green'), // null
]);
Coalescing assignment operator
A coalescing
assignment
operator ??= is also available.
The ??= operator can be used for conditionally writing to a variable if it is
null, or to a collection if the specified key is not present or has null
value.
This is similar to e1 = e1 ?? e2, with the important difference that e1 is
only evaluated once.
The ??= operator is very useful for initializing elements of a collection:
function get_counts_by_value(Traversable<string> $values): dict<string, int> {
$counts_by_value = dict[];
foreach ($values as $value) {
$counts_by_value[$value] ??= 0;
++$counts_by_value[$value];
}
return $counts_by_value;
}
function get_people_by_age(
KeyedTraversable<string, int> $ages_by_name,
): dict<int, vec<string>> {
$people_by_age = dict[];
foreach ($ages_by_name as $name => $age) {
$people_by_age[$age] ??= vec[];
$people_by_age[$age][] = $name;
}
return $people_by_age;
}
<<__EntryPoint>>
function main(): void {
$values = vec['foo', 'bar', 'foo', 'baz', 'bar', 'foo'];
\print_r(get_counts_by_value($values));
$people = dict[
'Arthur' => 35,
'Ford' => 110,
'Trillian' => 35,
'Zaphod' => 120,
];
\print_r(
get_people_by_age($people)
|> Dict\map($$, $names ==> Str\join($names, ', '))
);
}