Skip to content

Commit ebb8dae

Browse files
aphillipseemelicatamorphismgibson042
authored
[DESIGN] Effect of selectors on placeholders (#755)
* [DESIGN] Effect of selectors on placeholders Addresses #736, #747 DO NOT REVIEW YET == WORK IN PROGRESS * Flesh out background * Add a use case * Adding options and more user stories * Fix example typo * Update selection-declaration.md * Update selection-declaration.md * Update exploration/selection-declaration.md Co-authored-by: Eemeli Aro <[email protected]> * Update exploration/selection-declaration.md Co-authored-by: Tim Chevalier <[email protected]> * Address comments. * Update exploration/selection-declaration.md Co-authored-by: Tim Chevalier <[email protected]> * Update exploration/selection-declaration.md Co-authored-by: Tim Chevalier <[email protected]> * Update exploration/selection-declaration.md Co-authored-by: Tim Chevalier <[email protected]> * Update exploration/selection-declaration.md Co-authored-by: Richard Gibson <[email protected]> * Format tweak --------- Co-authored-by: Eemeli Aro <[email protected]> Co-authored-by: Tim Chevalier <[email protected]> Co-authored-by: Richard Gibson <[email protected]>
1 parent 6db127f commit ebb8dae

File tree

1 file changed

+360
-0
lines changed

1 file changed

+360
-0
lines changed
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
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

Comments
 (0)