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

Implement :unit as Proposed RECOMMENDED in the registry #922

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions spec/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,173 @@ together with the resolved options' values.

The _function_ `:currency` performs selection as described in [Number Selection](#number-selection) below.

### The `:unit` function

The _function_ `:unit` is **Proposed** for inclusion in the next release of this specification but has not yet been finalized.
The _function_ `:unit` is proposed to be a **RECOMMENDED** selector and formatter for unitized values,
that is, for numeric values associated with a unit of measurement.
This is a specialized form of numeric selection and formatting.

#### Operands

The _operand_ of the `:unit` function can be one of any number of
implementation-defined types,
each of which contains a numerical `value` plus a `unit`
or it can be a [Number Operand](#number-operands), as long as the _option_
`unit` is provided.

The value of the _operand_'s `unit` SHOULD be either a string containing a
valid [Unit Identifier](https://www.unicode.org/reports/tr35/tr35-general.html#unit-identifiers)
or an implementation-defined unit type.

A [Number Operand](#number-operands) without a `unit` _option_ results in a _Bad Operand_ error.

> [!NOTE]
> For example, in ICU4J, the type `com.ibm.icu.util.Measure` might be used
> as an _operand_ for `:unit` because it contains the `value` and `unit`.

> [!NOTE]
> For runtime environments that do not provide a ready-made data structure,
> class, or type for unit values, the implementation ought to provide
> a data structure, convenience function, or documentation on how to encode
> the value and unit for formatting.
> For example, such an implementation might define a "unit operand"
> to include a key-value structure with specific keys to be the
> local unit operand, which might look like the following:
> ```
> {
> "value": 123.45,
> "unit": "kilometer-per-hour"
> }
> ```

#### Options

Some _options_ do not have default values defined in this specification.
The defaults for these _options_ are implementation-dependent.
In general, the default values for such _options_ depend on the locale,
the unit,
the value of other _options_, or all of these.

> [!NOTE]
> The option `select` does not accept the value `ordinal` because selecting
> unit values using ordinal rules makes no sense.

The following options and their values are required to be available on the function `:unit`:
- `select`
- `plural` (default)
- `exact`
- `unit`
- valid [Unit Identifier](https://www.unicode.org/reports/tr35/tr35-general.html#unit-identifiers)
(no default)
macchiati marked this conversation as resolved.
Show resolved Hide resolved
- `usage` \[RECOMMENDED\]
- valid [Unicode Unit Preference](https://www.unicode.org/reports/tr35/tr35-info.html#unit-preferences)
(no default, see [Unit Conversion](#unit-conversion) below)
- `unitDisplay`
- `short` (default)
- `narrow`
- `long`
- `compactDisplay` (this option only has meaning when combined with the option `notation=compact`)
- `short` (default)
- `long`
- `notation`
- `standard` (default)
- `compact`
- `numberingSystem`
- valid [Unicode Number System Identifier](https://cldr-smoke.unicode.org/spec/main/ldml/tr35.html#UnicodeNumberSystemIdentifier)
(default is locale-specific)
- `signDisplay`
- `auto` (default)
- `always`
- `exceptZero`
- `negative`
- `never`
- `useGrouping`
- `auto` (default)
- `always`
- `never`
- `min2`
- `minimumIntegerDigits`
- ([digit size option](#digit-size-options), default: `1`)
- `minimumFractionDigits`
- ([digit size option](#digit-size-options))
- `maximumFractionDigits`
- ([digit size option](#digit-size-options))
- `minimumSignificantDigits`
- ([digit size option](#digit-size-options))
- `maximumSignificantDigits`
- ([digit size option](#digit-size-options))
- `roundingPriority`
- `auto` (default)
- `morePrecision`
- `lessPrecision`
- `roundingIncrement`
- 1 (default), 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, and 5000
- `roundingMode`
- `ceil`
- `floor`
- `expand`
- `trunc`
- `halfCeil`
- `halfFloor`
- `halfExpand` (default)
- `halfTrunc`
- `halfEven`

aphillips marked this conversation as resolved.
Show resolved Hide resolved
If the _operand_ of the _expression_ is an implementation-defined type,
such as the _resolved value_ of an _expression_ with a `:unit` _annotation_,
it can include _option_ values.
Comment on lines +675 to +677
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not clear here that not all "implementation-defined types" are necessarily supported as operands.

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'm not sure I understand. I don't think this needs to indicate that some types are not supported as operands.

This text is not about the operand. It's about the handling of option values within a resolved value. It is nearly boilerplate, since most of our functions have a variation of this sentence. In practice, it might be better to say something like the following, which doesn't care what the operand is or is not:

Suggested change
If the _operand_ of the _expression_ is an implementation-defined type,
such as the _resolved value_ of an _expression_ with a `:unit` _annotation_,
it can include _option_ values.
The _resolved value_ of an _expression_ with a `:unit` _annotation_
includes the result of _option resolution_.

Such a boilerplate would be altered if some of the options were required to be filtered by the function. unit is candidate for this in :unit (since the unit presumably is now part of the resolved operand).

These are included in the resolved _option_ values of the _expression_,
with _options_ on the _expression_ taking priority over any _option_ values of the _operand_.

> For example, the _placeholder_ in this _message_:
> ```
> .input {$n :unit unit=furlong minimumFractionDigits=2}
> {{{$n :unit minimumIntegerDigits=1}}}
> ```
> would have the resolved options:
> `{ unit: 'furlong', minimumFractionDigits: '2', minimumIntegerDigits: '1' }`.

#### Resolved Value

The _resolved value_ of an _expression_ with a `:unit` _function_
consist of an implementation-defined unit value
of the _operand_ of the annotated _expression_,
together with the resolved _options_ and their resolved values.

#### Selection

The _function_ `:unit` performs selection as described in [Number Selection](#number-selection) below.
Comment on lines +691 to +698
Copy link
Collaborator

Choose a reason for hiding this comment

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

These don't really work right together. If unit conversion is applied, the formatted value can be completely different from the "unit value of the operand of the annotated expression".

Copy link
Member

@macchiati macchiati Nov 20, 2024

Choose a reason for hiding this comment

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

Take 2 cases:

Suppose $n is an implementation-defined unit value, and the locale is en-US.

  1. If $n = {unit=kilogram, number=30} and we format {$n :unit maximumFractionDigits=0 usage=person} we'll get "66 pounds".
  2. If $n = {unit=pound, number=66.1387} and we format {$n :unit maximumFractionDigits=0 usage=person}, we'll also get "66 pounds".

So what is happening in both cases is that the numeric value for selection is equivalent to what the formatted value has. And that is what needs to be selected on, namely 66.

The wording we have for that in selection as described in

where resolvedSelector is the resolved value of a selector

So in both cases, the resolved value has to be 66.

There are of course other cases: n$ = {number=66.1387} and we format {$n :number maximumSignificantDigits=1} we'll get "70" (In English), and "٧٠" in Arabic. And that must match a selection key of 70.

So in this case, the resolved value has to be 70.

I think we haven't yet nailed 'resolved value', because the resolved value for selection and formatting ends up needing to be different than the resolved value for functional composition. But that we cannot 'resolve' at this point in the release.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I understand that what you describe is likely the intent here, but the resolved value description talks of "an implementation-defined unit value of the operand of the annotated expression", with no reference to any conversion possibly being made to it, and so it's reasonable to assume that e.g. your case 1 example would end up with the numerical value 30 being the value that selection is done on, and as the selection is the same as used for other numerical values, it doesn't know about needing to multiply the number by any such factor.

Note how in :math, the numeric value held in resolved value is explicitly modified by the add and subtract option values. We need something like that here.

Copy link
Member Author

Choose a reason for hiding this comment

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

If we permitted unit to convert, I agree that the resolved value should be modified. But I think we should do like :currency and only allow unit to compose with a numeric operand.

Copy link
Member

Choose a reason for hiding this comment

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

That works for me, and is safe and far simpler.

It would still be possible for a future version to allow that, but I think one would have to make a good case for doing so.


#### Unit Conversion
Copy link
Collaborator

Choose a reason for hiding this comment

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

According to the text above, unit conversion is also possible via the unit option. That should be mentioned here beyond its errant use in the example below, possibly along with some description of what happens if you define both unit and usage.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed.


Implementations MAY support conversion to the locale's preferred units via the `usage` _option_.
Implementing this _option_ is optional.
Not all `usage` values are compatible with a given unit.
Implementations SHOULD emit an _Unsupported Operation_ error if the requested conversion is not supported.

> For example, trying to convert a `length` unit (such as "meters")
> to a `volume` usage (which might be a unit akin to "liters" or "gallons", depending on the locale)
> could produce an _Unsupported Operation_ error.

Implementations MUST NOT substitute the unit without performing the associated conversion.

> For example, consider the value:
> ```
> {
> "value": 123.5,
> "unit": "meter"
> }
> ```
> The following _message_ might convert the formatted result to U.S. customary units
> in the `en-US` locale:
> ```
> You have {$v :unit usage=road maximumFractionDigits=0} to go.
> ```
> This can produce "You have 405 feet to go."



### Number Operands

The _operand_ of a number function is either an implementation-defined type or
Expand Down