|
| 1 | +# Effect of Selectors on Subsequent Placeholders |
| 2 | + |
| 3 | +Status: **Proposed** |
| 4 | + |
| 5 | +<details> |
| 6 | + <summary>Metadata</summary> |
| 7 | + <dl> |
| 8 | + <dt>Contributors</dt> |
| 9 | + <dd>@aphillips</dd> |
| 10 | + <dt>First proposed</dt> |
| 11 | + <dd>2024-03-27</dd> |
| 12 | + <dt>Pull Requests</dt> |
| 13 | + <dd>#000</dd> |
| 14 | + </dl> |
| 15 | +</details> |
| 16 | + |
| 17 | +## Objective |
| 18 | + |
| 19 | +_What is this proposal trying to achieve?_ |
| 20 | + |
| 21 | +Define what effect (if any) the _annotation_ of a _selector_ has on subsequent _placeholders_ |
| 22 | +that access the same _variable_. |
| 23 | + |
| 24 | +## Background |
| 25 | + |
| 26 | +_What context is helpful to understand this proposal?_ |
| 27 | + |
| 28 | +In MF2, we require that all _selectors_ have an _annotation_. |
| 29 | +The purpose of this requirement is to help ensure that a _selector_ on a given _operand_ |
| 30 | +is working with the same value as the _formatter_ eventually used for presentation |
| 31 | +of that _operand_. |
| 32 | +This is needed because the format of a value can have an effect on the grammar used |
| 33 | +in the localized _message_. |
| 34 | + |
| 35 | +For example, in English: |
| 36 | + |
| 37 | +> You have 1 mile to go. |
| 38 | +> You have 1.0 miles to go. |
| 39 | +
|
| 40 | +These messages might be written as: |
| 41 | + |
| 42 | +``` |
| 43 | +.input {$togo :integer} |
| 44 | +.match {$togo} |
| 45 | +0 {{You have arrived.}} |
| 46 | +one {{You have {$togo} mile to go.}} |
| 47 | +* {{You have {$togo} miles to go.}} |
| 48 | +
|
| 49 | +.input {$togo :number minimumFractionDigits=1} |
| 50 | +.match {$togo} |
| 51 | +0 {{You have arrived.}} |
| 52 | +one {{Unreachable in an English locale.}} |
| 53 | +* {{You have {$togo} miles to go.}} |
| 54 | +``` |
| 55 | + |
| 56 | +It is tempting to want to write these as a shorthand, with the _annotation_ in the _selector_: |
| 57 | + |
| 58 | +``` |
| 59 | +.match {$togo :integer} |
| 60 | +0 {{You have arrived.}} |
| 61 | +one {{You have {$togo} mile to go.}} |
| 62 | +* {{You have {$togo} miles to go.}} |
| 63 | +``` |
| 64 | + |
| 65 | +## Use-Cases |
| 66 | + |
| 67 | +_What use-cases do we see? Ideally, quote concrete examples._ |
| 68 | + |
| 69 | +1. As a user, I want my formatting to match my selector. |
| 70 | + This is one of the reasons why MF2 requires that selectors be annotated. |
| 71 | + When I write a selector, the point is to choose the pattern to use as a template |
| 72 | + for formatting the value being selected on. |
| 73 | + Mismatches between these are undesirable. |
| 74 | + |
| 75 | + ``` |
| 76 | + .match {$num :number minimumFractionDigits=1} |
| 77 | + one {{This case can never happen in an English locale}} |
| 78 | + * {{I expect this formats num with one decimal place: {$num}}} |
| 79 | + ``` |
| 80 | + |
| 81 | +2. As a user, I want to use the least amount of MF special syntax possible. |
| 82 | +3. As a user, I don't want to repeat formatting, particularly in large selection matrices. |
| 83 | + ``` |
| 84 | + .match {$num1 :integer} {$num2 :number minimumFractionDigits=1} |
| 85 | + 0 0 {{You have {$num1 :integer} ({$num2 :number minimumFractionDigits=1}) wildebeest.}} |
| 86 | + 0 one {{You have {$num1 :integer} ({$num2 :number minimumFractionDigits=1}) wildebeest.}} |
| 87 | + 0 * {{You have {$num1 :integer} ({$num2 :number minimumFractionDigits=1}) wildebeest.}} |
| 88 | + one 0 {{ }} |
| 89 | + one one {{ }} |
| 90 | + one * {{ }} |
| 91 | + // more cases for other locales that use two/few/many |
| 92 | + * 0 {{ }} |
| 93 | + * one {{ }} |
| 94 | + * * {{ }} |
| 95 | + ``` |
| 96 | + |
| 97 | +4. As a user (especially as a translator), I don't want to have to modify |
| 98 | + declarations and selectors to keep them in sync. |
| 99 | + ``` |
| 100 | + .input {$num :number minimumFractionDigits=1} |
| 101 | + .match {$num} |
| 102 | + * {{Shouldn't have to modify the selector}} |
| 103 | + ``` |
| 104 | + > Note that this is currently provided for by the spec. |
| 105 | +
|
| 106 | +5. As a user, I want to write multiple selectors using the same variable with different annotations. |
| 107 | + How do I know which one will format the placeholder later? |
| 108 | + ``` |
| 109 | + .match {$num :integer} {$num :number minimumFractionDigits=2} |
| 110 | + * * {{Which selector formats {$num}?}} |
| 111 | +
|
| 112 | + .match {$num :number minimumFractionDigits=2} {$num :integer} |
| 113 | + * * {{Which selector formats {$num}?}} |
| 114 | + ``` |
| 115 | + |
| 116 | + If both formats are needed in the message (presumably they are or why the selector), |
| 117 | + how does one reference one or the other? |
| 118 | + |
| 119 | + |
| 120 | +6. As a user I want to use the same operand for both formatting and selection, |
| 121 | + but use different functions or options for each. |
| 122 | + I don't want the options used for selection to mess up the formatting. |
| 123 | + |
| 124 | + For example, while LDML45 doesn't support selection on dates, |
| 125 | + it's easy to conceptualize a date selector at odds with the formatter: |
| 126 | + ``` |
| 127 | + .input {$d :datetime skeleton=yMMMdjm} |
| 128 | + .match {$d :datetime month=numeric} |
| 129 | + 1 {{Today is {$d} in cold cold {$d :datetime month=long} (trying to select on month)}} |
| 130 | + * {{Today is {$d}}} |
| 131 | + ``` |
| 132 | + |
| 133 | + Note that users can achieve this effect using a `.local`: |
| 134 | + ``` |
| 135 | + .input {$d :datetime skeleton=yMMMdjm} |
| 136 | + .local $monthSelect = {$d :datetime month=numeric} |
| 137 | + .match {$monthSelect} |
| 138 | + 1 {{No problem getting January and formatting {$d}}} |
| 139 | + * {{...}} |
| 140 | + ``` |
| 141 | + |
| 142 | +## Requirements |
| 143 | + |
| 144 | +_What properties does the solution have to manifest to enable the use-cases above?_ |
| 145 | + |
| 146 | +## Constraints |
| 147 | + |
| 148 | +_What prior decisions and existing conditions limit the possible design?_ |
| 149 | + |
| 150 | +## Proposed Design |
| 151 | + |
| 152 | +_Describe the proposed solution. Consider syntax, formatting, errors, registry, tooling, interchange._ |
| 153 | + |
| 154 | +## Alternatives Considered |
| 155 | + |
| 156 | +_What other solutions are available?_ |
| 157 | +_How do they compare against the requirements?_ |
| 158 | +_What other properties they have?_ |
| 159 | + |
| 160 | +### Do nothing |
| 161 | + |
| 162 | +In this alternative, selectors are independent of declarations. |
| 163 | +Selectors also do not affect the resolved value. |
| 164 | + |
| 165 | +Examples: |
| 166 | +``` |
| 167 | +.input {$n :integer} |
| 168 | +.match {$n :number minimumFractionDigits=2} |
| 169 | +* {{Formats '$n' as an integer: {$n}}} |
| 170 | +
|
| 171 | +.match {$n :integer} |
| 172 | +* {{If $n==1.2 formats {$n} as 1.2 in en-US}} |
| 173 | +``` |
| 174 | + |
| 175 | +**Pros** |
| 176 | +- No changes required. |
| 177 | +- `.local` can be used to solve problems with variations in selection and formatting |
| 178 | +- Supports multiple selectors on the same operand |
| 179 | + |
| 180 | +**Cons** |
| 181 | +- May require the user to annotate the operand for both formatting and selection. |
| 182 | +- Can produce a mismatch between formatting and selection, since the operand's formatting |
| 183 | + isn't visible to the selector. |
| 184 | + |
| 185 | +### Allow both local and input declarative selectors with immutability |
| 186 | + |
| 187 | +In this alternative, we modify the syntax to allow selectors to |
| 188 | +annotate an input variable (as with `.input`) |
| 189 | +or bind a local variable (as with `.local`). |
| 190 | +Either variable binding is immutable and results in a Duplicate Declaration error |
| 191 | +if it attempts to annotate a variable previously annotated. |
| 192 | + |
| 193 | +Example: |
| 194 | +``` |
| 195 | +.match {$input :function} $local = {$input :function} |
| 196 | +* * {{This annotates {$input} and assigns {$local} a value.}} |
| 197 | +
|
| 198 | +.match $local1 = {$input :function} $local2 = {$input :function2} |
| 199 | +* * {{This assigns two locals}} |
| 200 | +
|
| 201 | +.input {$input :function} |
| 202 | +.local $local = {$input :function} |
| 203 | +.match {$input :function} {$local :function} |
| 204 | +* * {{This produces two duplicate declaration errors.}} |
| 205 | +``` |
| 206 | + |
| 207 | +The ABNF change looks like: |
| 208 | +```abnf |
| 209 | +selector = expression / declaration |
| 210 | +declaration = s variable [s] "=" [s] expression |
| 211 | +``` |
| 212 | + |
| 213 | +**Pros** |
| 214 | +- Shorthand is consistent with the rest of the syntax |
| 215 | +- Shorthand version works intuitively with minimal input |
| 216 | +- Preserves immutability |
| 217 | +- Produces an error when users inappropriately annotate some items |
| 218 | + |
| 219 | +**Cons** |
| 220 | +- Selectors can't provide additional selection-specific options |
| 221 | + if the variable name is already in scope |
| 222 | +- Doesn't allow multiple selection on the same operand, e.g. |
| 223 | + ``` |
| 224 | + .input {$date :datetime skeleton=yMdjm} |
| 225 | + .match {$date :datetime field=month} {$date :datetime field=dayOfWeek} |
| 226 | + * * {{This message produces a Duplicate Declaration error |
| 227 | + even though selection is separate from formatting.}} |
| 228 | + ``` |
| 229 | + However, this design does allow for a local variable to be easily created |
| 230 | + for the purpose of selection. |
| 231 | + |
| 232 | +### Allow _immutable_ input declarative selectors |
| 233 | + |
| 234 | +In this alternative, selectors are treated as declaration-selectors. |
| 235 | +That is, an annotation in a selector works like a `.input`. |
| 236 | +This permits `.match` selectors to be a shorthand when no declarations exist. |
| 237 | +The option does not permit local variable declaration. |
| 238 | + |
| 239 | +It is not an error to re-declare a variable that is in scope. |
| 240 | +Instead the selector's annotation replaces what came before. |
| 241 | + |
| 242 | +``` |
| 243 | +.input {$num :integer} |
| 244 | +.match {$num :number minimumFractionDigits=1} |
| 245 | +* {{Formats {$num} like 1.0}} |
| 246 | +``` |
| 247 | + |
| 248 | +**Pros** |
| 249 | +- Shorthand version works intuitively with minimal typing. |
| 250 | + |
| 251 | +**Cons** |
| 252 | +- Violates immutability that we've established everywhere else |
| 253 | + |
| 254 | +### Allow _mutable_ input declarative selectors |
| 255 | + |
| 256 | +In this alternative, selectors are treated as declaration-selectors. |
| 257 | +That is, an annotation in a selector works like a `.input`. |
| 258 | +However, it is an error for the selector to try to modify a previous declaration |
| 259 | +(just as it is an error for a declaration to try to modify a previous declaration). |
| 260 | +This permits `.match` selectors to be a shorthand when no declarations exist. |
| 261 | + |
| 262 | +It is also an error for a selector to modify a previous selector. |
| 263 | +This implies that multiple selecton on the same operand is pointless. |
| 264 | + |
| 265 | +``` |
| 266 | +.match {$num :integer} |
| 267 | +* {{Formats {$num} as integer}} |
| 268 | +
|
| 269 | +.input {$num :integer} |
| 270 | +.match {$num :number maximumFractionDigits=0} |
| 271 | +* {{This message produces a Duplicate Declaration error}} |
| 272 | +
|
| 273 | +.input {$num :integer} {$num :number} |
| 274 | +* * {{This message produces a Duplicate Declaration error}} |
| 275 | +``` |
| 276 | + |
| 277 | +**Pros** |
| 278 | +- Shorthand version works intuitively with minimal typing |
| 279 | +- Preserves immutability |
| 280 | +- Produces an error when users inappropriately annotate some items |
| 281 | + |
| 282 | +**Cons** |
| 283 | +- Selectors can't provide additional selection-specific options |
| 284 | + if the value has already been annotated |
| 285 | +- Doesn't allow multiple selection on the same operand, e.g. |
| 286 | + ``` |
| 287 | + .input {$date :datetime skeleton=yMdjm} |
| 288 | + .match {$date :datetime field=month} {$date :datetime field=dayOfWeek} |
| 289 | + * * {{This message produces a Duplicate Declaration error |
| 290 | + even though selection is separate from formatting.}} |
| 291 | + ``` |
| 292 | + |
| 293 | +### Match on variables instead of expressions |
| 294 | + |
| 295 | +In this alternative, the `.match` syntax is simplified |
| 296 | +to work on variable references rather than expressions. |
| 297 | +This requires users to declare any selector using a `.input` or `.local` declaration |
| 298 | +before writing the `.match`: |
| 299 | + |
| 300 | +``` |
| 301 | +.input {$count :number} |
| 302 | +.match $count |
| 303 | +one {{You have {$count} apple}} |
| 304 | +* {{You have {$count} apples}} |
| 305 | +
|
| 306 | +.local $empty = {$theList :isEmpty} |
| 307 | +.match $empty |
| 308 | +true {{You bought nothing}} |
| 309 | +* {{You bought {$theList}!}} |
| 310 | +``` |
| 311 | + |
| 312 | +The ABNF change would look like: |
| 313 | +```diff |
| 314 | + match-statement = match 1*([s] selector) |
| 315 | +-selector = expression |
| 316 | ++selector = variable |
| 317 | +``` |
| 318 | + |
| 319 | +**Pros** |
| 320 | +- Overall the syntax is simplified. |
| 321 | +- Preserves immutability. |
| 322 | + |
| 323 | +**Cons** |
| 324 | +- A separate declaration is required for each selector. |
| 325 | + |
| 326 | +### Provide a `#`-like Feature |
| 327 | + |
| 328 | +(Copy-pasta adapted from @eemeli's proposal in #736) |
| 329 | + |
| 330 | +Make the `.match` expression also act as implicit declarations accessed by index position: |
| 331 | + |
| 332 | +``` |
| 333 | +.match {$count :number} |
| 334 | +one {{You have {$0} apple}} |
| 335 | +* {{You have {$0} apples}} |
| 336 | +``` |
| 337 | + |
| 338 | +Assigning values to `$0`, `$1`, ... would not conflict with any input values, |
| 339 | +as numbers are invalid `name-start` characters. |
| 340 | +That's by design so that we encourage at least _some_ name for each variable; |
| 341 | +here that's effectively provided by the `.match` expressions. |
| 342 | + |
| 343 | +ABNF would be modified: |
| 344 | +```diff |
| 345 | +-variable = "$" name |
| 346 | ++variable = "$" (name / %x30-39) |
| 347 | +``` |
| 348 | + |
| 349 | +...with accompanying spec language making numeric variables resolve to the `.match` selectors in placeholders, |
| 350 | +and a data model error otherwise. |
| 351 | + |
| 352 | +**Pros** |
| 353 | +- More ergonomic for most `.input` cases |
| 354 | +- Enables representation of many messages without any declarations |
| 355 | + |
| 356 | +**Cons** |
| 357 | +- Confusing that the operand name can't be used in the pattern? |
| 358 | + Removes some self-documentation from the pattern. |
| 359 | +- Requires the pattern to change if the selectors are modified. |
| 360 | +- Limits number of referenceable selectors to 10 (in the current form) |
0 commit comments