@@ -1028,25 +1028,18 @@ Alternative 5 is included for completeness.
10281028
10291029### Typed functions
10301030
1031- The following option aims to provide a general mechanism
1032- for custom function authors
1033- to specify how functions compose with each other.
1031+ Types are a way for users of a language
1032+ to reason about the kinds of data
1033+ that functions can operate on.
1034+ The most ambitious solution is to specify
1035+ a type system for MessageFormat functions.
10341036
1035- This is an extension of the "preservation model"
1036- from part 1 of this document.
1037-
1038- Here, ` ValueType ` is the most general type
1037+ ` ValueType ` is the most general type
10391038in a system of user-defined types.
10401039Using the function registry,
10411040each custom function could declare its own argument type
10421041and result type.
1043-
10441042This does not imply the existence of any static typechecking.
1045- A function passed the wrong type could signal a runtime error.
1046- This does require some mechanism for dynamically inspecting
1047- the type of a value.
1048-
1049- Consider Example B1 from part 1 of the document:
10501043
10511044Example B1:
10521045```
@@ -1055,103 +1048,31 @@ Example B1:
10551048 .local $z = {$y :uppercase}
10561049```
10571050
1058- Informally, we can write the type signatures for
1059- the three custom functions in this example:
1051+ In an informal notation,
1052+ the three custom functions in this example
1053+ have the following type signatures:
10601054
10611055```
10621056getAge : Person -> Number
10631057duration : Number -> String
10641058uppercase : String -> String
10651059```
10661060
1067- ` Number ` and ` String ` are assumed to be subtypes
1068- of ` MessageValue ` . Thus,
1069-
10701061The [ function registry data model] ( https://github.com/unicode-org/message-format-wg/blob/main/spec/registry.md )
1071- attempts to do some of this, but does not define
1072- the structure of the values produced by functions.
1062+ could be extended to define ` Number ` and ` String `
1063+ as subtypes of ` MessageValue ` .
1064+ A custom function author could use the custom
1065+ registry they define to define ` Person ` as
1066+ a subtype of ` MessageValue ` .
10731067
10741068An optional static typechecking pass (linting)
10751069would then detect any cases where functions are composed in a way that
1076- doesn't make sense. For example:
1077-
1078- Semantically invalid example:
1079- ```
1080- .local $z = {$person: uppercase}
1081- ```
1082-
1083- A person can't be converted to uppercase; or, ` :uppercase ` expects
1084- a ` String ` , not a ` Person ` . So an optional tool could flag this
1085- as an error, assuming that enough type information
1086- was included in the registry.
1087-
1088- The resolved value type is similar to what was proposed in
1089- [ PR 728] ( https://github.com/unicode-org/message-format-wg/pull/728/ ) .
1090-
1091- ``` ts
1092- interface MessageValue {
1093- formatToString(): string
1094- formatToX(): X // where X is an implementation-defined type
1095- getValue(): ValueType
1096- properties(): { [key : string ]: MessageValue }
1097- selectKeys(keys : string []): string []
1098- }
1099- ```
1100-
1101- The ` resolvedOptions() ` method is renamed to ` properties ` .
1102- This is to suggest that individual function implementations
1103- may not pass all of the options through into the resulting
1104- ` MessageValue ` .
1105-
1106- Instead of using ` unknown ` as the result type of ` getValue() ` ,
1107- we use ` ValueType ` , mentioned previously.
1108- Instead of using ` unknown ` as the value type for the
1109- ` properties() ` object, we use ` MessageValue ` ,
1110- since options can also be full ` MessageValue ` s with their own options.
1111-
1112- Because ` ValueType ` has a type tag,
1113- custom function implementations can easily
1114- signal dynamic errors if passed an operand of the wrong type.
1115-
1116- The advantage of this approach is documentation:
1117- with type names that can be used in type signatures
1118- specified in the registry,
1119- it's easy for users to reason about functions and
1120- understand which combinations of functions
1121- compose with each other.
1070+ doesn't make sense. The advantage of this approach is documentation.
11221071
11231072### Formatted value model (Composition operates on output)
11241073
1125- This is an elaboration on the "formatted model" from part 1.
1126-
1127- A less general solution is to have a single "resolved value"
1128- type, and specify that if function ` g ` consumes the resolved value
1129- produced by function ` f ` ,
1130- then ` g ` operates on the output of ` f ` .
1131-
1132- ```
1133- .local $x = {$num :number maxFrac=2}
1134- .local $y = {$x :number maxFrac=5 padStart=3}
1135- ```
1136-
1137- In this example, ` $x ` would be bound to the formatted result
1138- of calling ` :number ` on ` $num ` . So the ` maxFrac ` option would
1139- be "lost" and when determining the value of ` $y ` , the second
1140- set of options would be used.
1141-
1142- For built-ins, it suffices to define ` ValueType ` as something like:
1143-
1144- ```
1145- FormattedNumber | FormattedDateTime | String
1146- ```
1147-
1148- because no information about the input needs to be
1149- incorporated into the resolved value.
1150-
1151- However, to make it possible for custom functions to return
1152- a wider set of types, a wider ` ValueType ` definition would be needed.
1153-
1154- The ` MessageValue ` definition would look as in #728 , but without
1074+ To implement the "formatted value" model,
1075+ the ` MessageValue ` definition would look as in [ PR 728] ( https://github.com/unicode-org/message-format-wg/pull/728 ) , but without
11551076the ` resolvedOptions() ` method:
11561077
11571078``` ts
@@ -1165,31 +1086,13 @@ interface MessageValue {
11651086
11661087` MessageValue ` is effectively a ` ValueType ` with methods.
11671088
1168- Using this definition would make some of the use cases from part 1
1089+ Using this definition would make some of the use cases
11691090impractical.
11701091
1171- ### Preservation model (composition can operate on input and options)
1172-
1173- This is an extension of
1174- the "preservation model" from part 1,
1175- if resolved options are included in the output.
1176- This model can also be thought of as functions "pipelining"
1177- the input through multiple calls.
1178-
1179- A JSON representation of an example resolved value might be:
1180- ```
1181- {
1182- input: { type: "number", value: 1 },
1183- output: { type: "FormattedNumber", value: FN }
1184- properties: { "maximumFractionDigits": 2 }
1185- }
1186- ```
1092+ ### Preservation model (Composition can operate on input and options)
11871093
1188- (The number "2" is shown for brevity, but it would
1189- actually be a ` MessageValue ` itself.)
1190-
1191- where ` FN ` is an instance of an implementation-specific
1192- ` FormattedNumber ` type, representing the number 1.
1094+ In the preservation model,
1095+ functions "pipeline" the input through multiple calls.
11931096
11941097The resolved value interface would include both "input"
11951098and "output" methods:
@@ -1205,6 +1108,18 @@ interface MessageValue {
12051108}
12061109```
12071110
1111+ Compared to PR 728:
1112+ The ` resolvedOptions() ` method is renamed to ` properties ` .
1113+ Individual function implementations
1114+ choose which options to pass through into the resulting
1115+ ` MessageValue ` .
1116+
1117+ Instead of using ` unknown ` as the result type of ` getValue() ` ,
1118+ we use ` ValueType ` , mentioned previously.
1119+ Instead of using ` unknown ` as the value type for the
1120+ ` properties() ` object, we use ` MessageValue ` ,
1121+ since options can also be full ` MessageValue ` s with their own options.
1122+
12081123Without a mechanism for type signatures,
12091124it may be hard for users to tell which combinations
12101125of functions compose without errors,
@@ -1224,34 +1139,12 @@ Consider (this suggestion is from Elango Cheran):
12241139 {{$x} {$y}}
12251140```
12261141
1227- If ` $num ` is ` 0.33333 ` ,
1228- then the result of formatting would be
1229-
1230- ```
1231- 0.33 000.33333
1232- ```
12331142
1234- An extra argument to function implementations,
1235- ` pipeline ` , would be added.
12361143
12371144` .pipeline ` would be a new keyword that acts like ` .local ` ,
12381145except that if its expression has a function annotation,
1239- the formatter would pass in ` true ` for the ` pipeline `
1240- argument to the function implementation.
1241-
1242- The ` resolvedOptions() ` method should be ignored if ` pipeline `
1243- is ` false ` .
1244-
1245- ``` ts
1246- interface MessageValue {
1247- formatToString(): string
1248- formatToX(): X // where X is an implementation-defined type
1249- getInput(): MessageValue
1250- getOutput(): unknown
1251- properties(): { [key : string ]: MessageValue }
1252- selectKeys(keys : string []): string []
1253- }
1254- ```
1146+ the formatter would apply the "preservation model" semantics
1147+ to the function.
12551148
12561149### Don't allow composition for built-in functions
12571150
@@ -1263,19 +1156,12 @@ number : Number -> FormattedNumber
12631156date : Date -> FormattedDate
12641157```
12651158
1266- Then it would be a runtime error to pass a ` FormattedNumber ` into ` number `
1267- or to pass a ` FormattedDate ` into ` date ` .
1268-
1269- The resolved value type would look like:
1159+ The resolved value type would be the same as
1160+ in the formatted value model.
12701161
1271- ``` ts
1272- interface MessageValue {
1273- formatToString(): string
1274- formatToX(): X // where X is an implementation-defined type
1275- getValue(): ValueType
1276- selectKeys(keys : string []): string []
1277- }
1278- ```
1162+ The difference is that built-in functions
1163+ would not accept a "formatted result"
1164+ (would signal a runtime error in these cases).
12791165
12801166As with the formatted value model, this restricts the
12811167behavior of custom functions.
0 commit comments