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['blue'] is not defined
]);
Output
Vec
(
    [0] => 10
    [1] => a string
)
Vec
(
    [0] => 10
    [1] => -200
    [2] => -300
)

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
]);
Output
Vec
(
    [0] => 10
    [1] => 
    [2] => -300
    [3] => 
)

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.

$arr[++$i] ??= 42;              // $i is incremented once
$arr[++$i] = $arr[++$i] ?? 42;  // $i is incremented twice

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, ', '))
  );
}
Output
Dict
(
    [foo] => 3
    [bar] => 2
    [baz] => 1
)
Dict
(
    [35] => Arthur, Trillian
    [110] => Ford
    [120] => Zaphod
)