|
| 1 | +# Disclaimer |
| 2 | +This feature is on its way to be deployed. Most recent HHVM versions no longer need the `EnableUnstableFeatures` file attribute |
| 3 | +to support this. More information available at https://github.com/hhvm/user-documentation/pull/1122 |
| 4 | + |
| 5 | +## Values v. Bindings |
| 6 | + |
| 7 | +With [enum types](/hack/built-in-types/enum) and [enum classes](/hack/built-in-types/enum-class), most of the focus is given to their values. |
| 8 | +Expressions like `E::A` denote the value of `A` in `E`, but the fact that `A` was used to access it is lost. |
| 9 | + |
| 10 | +```EnumClassLabelIntro.hack no-auto-output |
| 11 | +enum E : int { |
| 12 | + A = 42; |
| 13 | + B = 42; |
| 14 | +} |
| 15 | +
|
| 16 | +function f(E $value) : void { |
| 17 | + switch($value) { |
| 18 | + case E::A: echo "A "; break; |
| 19 | + case E::B: echo "B "; break; |
| 20 | + } |
| 21 | + echo $value . "\n"; |
| 22 | +} |
| 23 | +``` |
| 24 | +In this example, both `f(E::A)` and `f(E::B)` will echo `A 42` because `E::A` and `E::B` are effectively the value `42` and nothing more. |
| 25 | + |
| 26 | +## Enum Class Labels |
| 27 | + |
| 28 | +Sometimes, the binding that was used to access a value from an enumeration is as important as the value itself. We might want to know that `A` |
| 29 | +was used to access `E::A`. Enum types provides a partial solution to this with the `getNames` static method, but it is only |
| 30 | +safe to call if all the values of the enumeration are distinct. |
| 31 | + |
| 32 | +Enum classes provides a way to do this by using the newly introduced *Enum Class Label* expressions. For each value defined in an enum class, a corresponding |
| 33 | +label is defined. A label is a handle to access the related value. Think of it as an indirect access. Consider the following example: |
| 34 | + |
| 35 | +```EnumClassLabel.definition.hack no-auto-output |
| 36 | +
|
| 37 | +// We are using int here for readability but it works for any type |
| 38 | +enum class E : int { |
| 39 | + int A = 42; |
| 40 | + int B = 42; |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +This example defines two constants `E::A: \HH\MemberOf<E, int>` and `E::B: \HH\MemberOf<E, int>`. Enum class labels add more definitions to the mix: |
| 45 | + |
| 46 | +- `E#A: \HH\EnumClass\Label<E, int>` is the label to access `E::A` |
| 47 | +- `E#B: \HH\EnumClass\Label<E, int>` is the label to access `E::B` |
| 48 | +- `E::nameOf` is a static method expecting a label and returning its string representation: `E::nameOf(E#A) = "A"` |
| 49 | +- `E::valueOf` is a static method expecting a label and returning its value: `E::valueOf(E#A) = E::A` |
| 50 | + |
| 51 | +So we can rewrite the earlier example in a more resilient way: |
| 52 | +```EnumClassLabel.example.hack no-auto-output |
| 53 | +<<file:__EnableUnstableFeatures('enum_class_label')>> // temp |
| 54 | +
|
| 55 | +function full_print(\HH\EnumClass\Label<E, int> $label) : void { |
| 56 | + echo E::nameOf($label) . " "; |
| 57 | + echo E::valueOf($label) . "\n"; |
| 58 | +} |
| 59 | +
|
| 60 | +function partial_print(\HH\MemberOf<E, int> $value) : void { |
| 61 | + echo $value . "\n"; |
| 62 | +} |
| 63 | +``` |
| 64 | +Now, `full_print(E#A)` will echo `A 42` and `full_print(E#B)` will echo `B 42`. |
| 65 | + |
| 66 | +## Full v. Short Labels |
| 67 | + |
| 68 | +We refer to labels like `E#A` as *fully qualified* labels: the programmer wrote the full enum class name. |
| 69 | +However there are some situations where Hack can infer the class name; for example, |
| 70 | +the previous calls could be written as `full_print(#A)` and `full_print(#B)`, leaving `E` implicit. |
| 71 | +This is only allowed when there is enough type information to infer the right enum class name. For example, `$x = #A` is not allowed and will result in a type error. |
| 72 | + |
| 73 | +### Special case of function calls |
| 74 | + |
| 75 | +When the first argument of a function is a label, we provide an alternative notation to call it. |
| 76 | +```EnumClassLabel.alt.hack no-auto-output |
| 77 | +<<file:__EnableUnstableFeatures('enum_class_label')>> // temp |
| 78 | +
|
| 79 | +function set<T>(\HH\EnumClass\Label<E, T> $label, T $data) : void { |
| 80 | + // setting $data into some storage using $label as a key |
| 81 | +} |
| 82 | +
|
| 83 | +// all these calls are equivalent |
| 84 | +function all_the_same(): void { |
| 85 | + set(E#A, 42); |
| 86 | + set(#A, 42); |
| 87 | + set#A(42); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +As you can see, the short name can be written *before* the opening parenthesis of the function call. This only works for a single label argument, which must be the first one. |
| 92 | + |
| 93 | +## Known corner cases |
| 94 | + |
| 95 | +### The `#` character is no longer a single-line comment |
| 96 | +This feature relies on the fact that Hack and HHVM no longer consider the character `#` as a single-line comment. Please use `//` for such purpose. |
| 97 | + |
| 98 | +### Labels and values cannot be exchanged |
| 99 | +If a method is expecting a label, one cannot pass in a value, and vice versa: `full_print(E::A)` will result in a type error and so will `partial_print(E#A)`. |
| 100 | + |
| 101 | +### `MemberOf` is covariant, `Label` is invariant |
| 102 | +A label should be considered as a way to attach a type to a binding. Therefore it is invariant: `E#A` is not of type `\HH\EnumClass\Label<E, arraykey>`. |
| 103 | +This can be misleading at first, because `\HH\MemberOf` is covariant (it is data after all): `E::A` is of type `\HH\MemberOf<E, arraykey>`. |
0 commit comments