You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
formatToX():X// where X is an implementation-defined type
1103
+
getValue():ValueType
1104
+
properties(): { [key:string]:MessageValue }
1105
+
selectKeys(keys:string[]):string[]
1106
+
}
1107
+
```
1108
+
1109
+
The `resolvedOptions()` method is renamed to `properties`.
1110
+
This is to suggest that individual function implementations
1111
+
may not pass all of the options through into the resulting
1112
+
`MessageValue`.
1113
+
1114
+
Instead of using `unknown` as the result type of `getValue()`,
1115
+
we use `ValueType`, mentioned previously.
1116
+
Instead of using `unknown` as the value type for the
1117
+
`properties()` object, we use `MessageValue`,
1118
+
since options can also be full `MessageValue`s with their own options.
1119
+
1120
+
Because `ValueType` has a type tag,
1121
+
custom function implementations can easily
1122
+
signal dynamic errors if passed an operand of the wrong type.
1123
+
1124
+
The advantage of this approach is documentation:
1125
+
with type names that can be used in type signatures
1126
+
specified in the registry,
1127
+
it's easy for users to reason about functions and
1128
+
understand which combinations of functions
1129
+
compose with each other.
1130
+
1131
+
### Formatted value model (Composition operates on output)
1132
+
1133
+
This is an elaboration on the "formatted model" from part 1.
1134
+
1135
+
A less general solution is to have a single "resolved value"
1136
+
type, and specify that if function `g` consumes the resolved value
1137
+
produced by function `f`,
1138
+
then `g` operates on the output of `f`.
1139
+
1140
+
```
1141
+
.local $x = {$num :number maxFrac=2}
1142
+
.local $y = {$x :number maxFrac=5 padStart=3}
1143
+
```
1144
+
1145
+
In this example, `$x` would be bound to the formatted result
1146
+
of calling `:number` on `$num`. So the `maxFrac` option would
1147
+
be "lost" and when determining the value of `$y`, the second
1148
+
set of options would be used.
1149
+
1150
+
For built-ins, it suffices to define `ValueType`as something like:
1151
+
1152
+
```
1153
+
FormattedNumber | FormattedDateTime | String
1154
+
```
1155
+
1156
+
because no information about the input needs to be
1157
+
incorporated into the resolved value.
1158
+
1159
+
However, to make it possible for custom functions to return
1160
+
a wider set of types, a wider `ValueType` definition would be needed.
1161
+
1162
+
The `MessageValue` definition would look as in #728, but without
1163
+
the `resolvedOptions()` method:
1164
+
1165
+
```ts
1166
+
interfaceMessageValue {
1167
+
formatToString():string
1168
+
formatToX():X// where X is an implementation-defined type
1169
+
getValue():ValueType
1170
+
selectKeys(keys:string[]):string[]
1171
+
}
1172
+
```
1173
+
1174
+
`MessageValue` is effectively a `ValueType` with methods.
1175
+
1176
+
Using this definition would make some of the use cases from part 1
1177
+
impractical.
1178
+
1179
+
### Preservation model (composition can operate on input and options)
1180
+
1181
+
This is an extension of
1182
+
the "preservation model" from part 1,
1183
+
if resolved options are included in the output.
1184
+
This model can also be thought of as functions "pipelining"
1185
+
the input through multiple calls.
1186
+
1187
+
A JSON representation of an example resolved value might be:
1188
+
```
1189
+
{
1190
+
input: { type: "number", value: 1 },
1191
+
output: { type: "FormattedNumber", value: FN }
1192
+
properties: { "maximumFractionDigits": 2 }
1193
+
}
1194
+
```
1195
+
1196
+
(The number "2" is shown for brevity, but it would
1197
+
actually be a `MessageValue` itself.)
1198
+
1199
+
where `FN` is an instance of an implementation-specific
1200
+
`FormattedNumber` type, representing the number 1.
1201
+
1202
+
The resolved value interface would include both "input"
1203
+
and "output" methods:
1204
+
1205
+
```ts
1206
+
interfaceMessageValue {
1207
+
formatToString():string
1208
+
formatToX():X// where X is an implementation-defined type
1209
+
getInput():ValueType
1210
+
getOutput():ValueType
1211
+
properties(): { [key:string]:MessageValue }
1212
+
selectKeys(keys:string[]):string[]
1213
+
}
1214
+
```
1215
+
1216
+
Without a mechanism for type signatures,
1217
+
it may be hard for users to tell which combinations
1218
+
of functions compose without errors,
1219
+
and for implementors to document that information
1220
+
for users.
1221
+
1222
+
### Allow both kinds of composition (with different syntax)
1223
+
1224
+
By introducing new syntax, the same function could have
1225
+
either "preservation" or "formatted value" behavior.
1226
+
1227
+
Consider (this suggestion is from Elango Cheran):
1228
+
1229
+
```
1230
+
.local $x = {$num :number maxFrac=2}
1231
+
.pipeline $y = {$x :number maxFrac=5 padStart=3}
1232
+
{{$x} {$y}}
1233
+
```
1234
+
1235
+
If `$num` is `0.33333`,
1236
+
then the result of formatting would be
1237
+
1238
+
```
1239
+
0.33 000.33333
1240
+
```
1241
+
1242
+
An extra argument to function implementations,
1243
+
`pipeline`, would be added.
1244
+
1245
+
`.pipeline` would be a new keyword that acts like `.local`,
1246
+
except that if its expression has a function annotation,
1247
+
the formatter would pass in `true` for the `pipeline`
1248
+
argument to the function implementation.
1249
+
1250
+
The `resolvedOptions()` method should be ignored if `pipeline`
1251
+
is `false`.
1252
+
1253
+
```ts
1254
+
interfaceMessageValue {
1255
+
formatToString():string
1256
+
formatToX():X// where X is an implementation-defined type
1257
+
getInput():MessageValue
1258
+
getOutput():unknown
1259
+
properties(): { [key:string]:MessageValue }
1260
+
selectKeys(keys:string[]):string[]
1261
+
}
1262
+
```
1263
+
1264
+
### Don't allow composition for built-in functions
1265
+
1266
+
Another option is to define the built-in functions this way,
1267
+
notionally:
1268
+
1269
+
```
1270
+
number : Number -> FormattedNumber
1271
+
date : Date -> FormattedDate
1272
+
```
1273
+
1274
+
Then it would be a runtime error to pass a `FormattedNumber` into `number`
1275
+
or to pass a `FormattedDate` into `date`.
1276
+
1277
+
The resolved value type would look like:
1278
+
1279
+
```ts
1280
+
interfaceMessageValue {
1281
+
formatToString():string
1282
+
formatToX():X// where X is an implementation-defined type
1283
+
getValue():ValueType
1284
+
selectKeys(keys:string[]):string[]
1285
+
}
1286
+
```
1287
+
1288
+
As with the formatted value model, this restricts the
1289
+
behavior of custom functions.
1290
+
1291
+
### Non-alternative: Allow composition in some implementations
1292
+
1293
+
Allow composition only if the implementation requires functions to return a resolved value as defined in [PR 728](https://github.com/unicode-org/message-format-wg/pull/728).
0 commit comments