Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hybrid option to selection-declaration.md #870

Merged
merged 8 commits into from
Aug 27, 2024
66 changes: 66 additions & 0 deletions exploration/selection-declaration.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,69 @@ and a data model error otherwise.
Removes some self-documentation from the pattern.
- Requires the pattern to change if the selectors are modified.
- Limits number of referenceable selectors to 10 (in the current form)

### Hybrid approach: Match may mutate, no duplicates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be titled incorrectly?

When we say "immutable" in the other options, we mean that the nothing can be annotated twice. Anything can be annotated once (in fact, we insist on it).

Suggested change
### Hybrid approach: Match may mutate, no duplicates
### Hybrid approach: Allow _immutable_ input-declarative selectors but forbid duplicates

That is, I think you mean that this is an error:

.input {$foo :integer}
.match {$foo :anything_else}
* {{Can't redeclare $foo}}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually opening it up beyond that, that is

.input {$in :number}
.local $count = {$in :modify add=-1}
.match {$count :integer}
one {{You have {$count} whole apple.}}
* {{You have {$count} whole apples.}}

would be equivalent to:

.input {$in :number}
.local $count = {$in :modify add=-1}
.local $count2 = {$count :integer} // logically inserted
.match {$count2} // logically replaced
one {{You have {$count2} whole apple.}} // logically replaced
* {{You have {$count2} whole apples.}} // logically replaced

(And that's how it could be implemented internally, without internal mutation.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using only functions in the standard set, would this be a valid restatement of that example, or an error?

.input {$in :number}
.local $count = {$in :number signDisplay=always} // affects the formatting
                                                 // but not selection on purpose
.match {$count :integer} // uses :integer for selection, might be a mutability error?
one {{You have {$count} whole apple}}
*   {{You have {$count} whole apples}}

I don't want to bring in the whole resolved value discussion, just the discussion of immutability, but I think we intended this to be an error currently. Mutator functions, such as your :modify, are super-interesting, in that they aren't really formatters or selectors.

Playing devil's advocate:

.input {$in :number}
.local $count = {$in :modify add=-1}
.local $count2 = {$count :integer}      // typed explicitly by the user
.match {$count2}                        // ... and used to select; can I annotate?
one {{You have {$count2} whole apple.}} // does this get implicitly changed?
* {{You have {$count2} whole apples.}}

Also, what about non-consonant selectors:

.input {$now :datetime}
.match {$now :before field=hour value=12} // is it morning? No effect on display.
true {{You still have time to do it before lunch.}}
*    {{It's already {$now :time} on {$now :date}}}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My proposal is to add mutability in a very limited circumstance, in the match statement. Nothing before or after would change; the previous .local statements and the variant messages couldn't change a defined value.

.input {$count :number}
// do something else with count
.match {$count :integer} // mutates $count
one {{You have {$count} whole apple}}
\*   {{You have {$count} whole apples}}

I realize that this is a change; however, because we forbid $count being used twice in the .match, I believe it prevents any ill-effects, while also avoiding the truly horrible disconnect between formatting and selection.

I don't see any cases where the relaxation of mutability in the match statement will cause problems. I'm of course interested to see if anyone can come up with any.

There is a weaker variant of this proposal:

Weaker Option

Disallow annotations on match selector operands that have a previous .input or .local. Allow them if there are no such .input or .local statements: as equivalent to an input statement

.match {$count :integer}
one {{You have {$count} whole apple}}
\*   {{You have {$count} whole apples}}

Which would be equivalent to:

.input {$count :integer}
.match {$count}
one {{You have {$count} whole apple}}
\*   {{You have {$count} whole apples}}

And the following would be an error.

NOTE: I'm trying to keep this example to the minimum. The match would or variant messages would have to be more elaborate to actually need the "something else".

// ERROR
.input {$count :number}
// do something else with count
.match {$count :integer}
one {{You have {$count} whole apple}}
\*   {{You have {$count} whole apples}}

It would have to be rewritten as:

.input {$count :number}
// do something else with count
.local $count2 = {$count :integer}
.match {$count2}
one {{You have {$count2} whole apple}}
\*   {{You have {$count2} whole apples}}

That would keep everything immutable, but require rewrites using an extra statement and change in names, for any selector operand that needs a .local


In this alternative, in a `.match` statement:

1. variables are mutated by their annotation
2. no variable can be the operand in two selectors

This keeps most messages more concise, producing the expected results in Example 1.

#### Example 1

```
.match {$count :integer}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is already submitted, and we are balloting on it.
But this is risky.

Think of a parameter of type Person, with first / last name, DOB, gender, etc.

.match {$host :gender}
female {You got an invitation from {$host} to her party.}
...

One would really expect something like "... from Maria to her ..."
But because the .match "binds" the parameter with :gender now we get "... from female to her ..."
Can be solved with a local declaration.
But surprising.

So we fix the plural (:number) surprise (select on something and render something else), and we replace it with a different surprise.

Copy link
Member

@aphillips aphillips Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as individual)

Presumably :gender as a selector is not a formatting function. The default formatting function for $host of type "person" would be implementation defined and presumably not be the :gender selector, despite the annotation. :number or :integer are interesting because they are both selectors and formatters. I can imagine other values behaving like that as well (function defaulting is currently implementation defined)

Compound objects, such as person names or measurements, probably are complicated because of the reasonable desire to do query-style selection (e.g. the plural category of a currency value's number or, conversely, select based on the currency value's currencyCode).

We haven't fully baked "resolved value" yet and this is part of that discussion.

One last thought: is this really that different from some of the other options being balloted? This consideration would go in the pro/con buckets for each.


(chair hat ON)

Note that we are balloting selection's relationship to declarations, but the voting is only advisory to a technical conversation. Also, you are encouraged to vote against solutions you don't like and to discuss them on the discussion thread.

one {{You have {$count} whole apple.}}
* {{You have {$count} whole apples.}}
```
is precisely equivalent to:

#### Example 2
```
.local $count2 = {$count :integer}
.match {$count2}
one {{You have {$count2} whole apple.}}
* {{You have {$count2} whole apples.}}
```

This avoids the serious problems with mismatched selection and formats
as in Example 1 under "Do Nothing", whereby the input of `count = 1.2`,
results the malformed "You have 1.2 whole apple."

Due to clause 2, this requires users to declare any selector using a `.input` or `.local` declaration
before writing the `.match`. That is, the following is illegal.

#### Example 3
```
.match {$count <anything>}{$count <anything>}
```
It would need to be rewritten as something along the lines of:

#### Example 4
```
.local $count3 = {$count}
.match {$count <anything1>}{$count3 <anything2>}
```
Notes:
- The number of times the same variable is used twice in a match (or the older Select) is vanishingly small. Since it is an error — and the advice to fix is easy — that will prevent misbehavior.
- There would be no change to the ABNF; but there would be an additional constraint in the spec, and relaxation of immutability within the .match statement.

**Pros**
- No new syntax is required
- Preserves immutability before and after the .match statement
- Avoids the serious problem of mismatch of selector and format of option "Do Nothing"
- Avoids the extra syntax of option "Allow both local and input declarative selectors with immutability"
- Avoids the problem of multiple variables in "Allow immutable input declarative selectors"
- Is much more consise than "Match on variables instead of expressions", since it doesn't require a .local or .input for every variable with options
- Avoids the readability issues with "Provide a #-like Feature"

**Cons**
aphillips marked this conversation as resolved.
Show resolved Hide resolved
- Complexity: `.match` means more than one thing
- Complexity: `.match` implicitly creates a new lexical scope
- Violates immutability that we've established everywhere else
- Requires additional `.local` declarations in cases where a variable would occur twice
such as `.match {$date :date option=monthOnly} {$date :date option=full}`