Introduce readonly variant of idx
[hiphop-php.git] / hphp / system / php / misc / idx.php
blob93a934447147e737387f82e291613cd0cccc11b4
1 <?hh // partial
2 <<file: __EnableUnstableFeatures("readonly")>>
4 namespace HH {
6 /**
7 * Returns the value at a key of a KeyedContainer, if this key is present.
8 * This function simplifies the common pattern of checking for a key in
9 * a KeyedContainer and using a default value if the key is not present.
10 * You should NOT use `idx` as a replacement for accessing elements
11 * of KeyedContainers, since this makes the code harder to reason about.
13 * `idx` is used to try and index into a KeyedContainer, and return either
14 * the value found at this key or some default. Writing this out the
15 * long way would look like this:
17 * ```
18 * C\contains_key($keyedcontainer, 'key') ? $keyedcontainer['key'] : $default;
19 * ```
21 * This is verbose, and duplicates the variable name and index name, which can
22 * lead to errors. With `idx`, you can simplify the expression:
24 * ```
25 * idx($keyedcontainer, 'key', $default);
26 * ```
28 * The value `$default` is optional, and defaults to null if unspecified.
30 * The first argument is permitted to be null; if it is null,
31 * `idx` will always return `$default`.
33 * The second argument is permitted to be null; if it is null,
34 * `idx` will always return `$default`.
36 * Just as an aside, Hack has a null coalesce operator which interacts with
37 * with subscripting operations in an unusual way.
38 * The important difference between `$dict['key'] ?? $default` and
39 * `idx($dict, 'key', $default)` is that the `??` operator will also
40 * resolve the `$default` if the value held in `$dict['key']` is null.
41 * `idx`, in contrast, will return the null stored at 'key' instead, since 'key' is present.
43 * If you notice yourself accessing deeply nested KeyedContainers like this:
45 * ```
46 * // $dict['key1']['key2']['key3'], but it resolves to null when a key is missing.
47 * idx($dict, 'key1', dict[]) |> idx($$, 'key2', dict[]) |> idx($$, 'key3');
48 * ```
50 * it may be more natural to use `??` instead.
52 * ```
53 * $dict['key1']['key2']['key3'] ?? null;
54 * ```
56 * You should NOT use `idx` as a general replacement for accessing KeyedContainer
57 * indices. If you expect 'key' to always exist, do not use `idx`!
59 * ```
60 * // COUNTEREXAMPLE
61 * $dict['key'] = some_function();
62 * // code...
63 * $y = idx($dict, 'key');
64 * ```
66 * This code is misleading, since the default value of `idx` (null) will never be used.
67 * This confuses the reader and leads to annoying nullchecks in the code using `$y`.
68 * Since we know that 'key' should / must be present, it is best to stick to
69 * indexing with the subscript operator like so.
71 * ```
72 * $dict['key'];
73 * ```
75 * This will throw an OutOfBoundsException if the 'key' is somehow not present.
76 * This is a good thing, since it will alert you that your mental model
77 * of the code is wrong, instead of continuing with `null` (or the default) silently.
79 * `idx` is for default selection, not a blanket replacement for array access.
81 * If you are tasked with fixing a bug that is caused by an OutOfBoundsException
82 * it is often tempting to use `idx` and set a default in place.
83 * This is hiding the underlying problem.
84 * Chances are that the programmer before you actually expected the key to always be present.
85 * Try to figure out why this might be the case.
86 * If this KeyedContainer is being used to access keys which are static in the source code,
87 * it might be helpful to change the code to use a shape() instead if possible.
88 * This will instruct the typechecker to validate that the keys are present.
90 * The following behavior is deprecated and should not be relied upon.
91 * Because of backwards compatiblity, `idx` treats strings like arrays of characters.
92 * This is not allowed by the typechecker, since string is not a KeyedContainer.
93 * Indexing into a string can be done safely like so: `$string[4] ?? null`.
95 * @param ?KeyedContainer $arr - KeyedContainer to look for an index in.
96 * @param ?arraykey $idx - Index to check for.
97 * @param mixed $default - Default value to return if index is not found. By
98 * default, this is null.
99 * @return mixed Value at array index if it exists,
100 * or the default value if not.
102 function idx($arr, $idx, $default=null)[] {
103 if (\HH\is_any_array($arr)) {
104 return \hphp_array_idx($arr, $idx, $default);
107 if ($idx !== null) {
108 if (\is_object($arr)) {
109 if ($arr is \ConstIndexAccess) {
110 if ($arr->containsKey($idx)) {
111 return $arr[$idx];
113 } else if ($arr is \ConstSet) {
114 if ($arr->contains($idx)) {
115 return $idx;
118 } else if (\is_string($arr)) {
119 if (isset($arr[$idx])) {
120 return $arr[(int)$idx];
125 return $default;
128 function idx_readonly<Tk as arraykey, Tv>(readonly $arr, $idx, $default=null)[] : readonly Tv {
129 // readonlyness is not enforced in systemlib so we can pass it here without error
130 return readonly idx($arr, $idx, $default);