11# Function Composition
22
3- Status: ** Proposed **
3+ Status: ** Obsolete **
44
55<details >
66 <summary>Metadata</summary>
@@ -11,22 +11,20 @@ Status: **Proposed**
1111 <dd>2024-03-26</dd>
1212 <dt>Pull Requests</dt>
1313 <dd>#753</dd>
14+ <dd>#806</dd>
1415 </dl>
1516</details >
1617
17- ## Objective
18+ ## Objectives
1819
19- _ What is this proposal trying to achieve?_
20+ * Present a complete list of alternative designs for how to
21+ provide the machinery for function composition.
22+ * Create a shared vocabulary for discussing these alternatives.
2023
21- ### Non-goal
22-
23- The objective of this design document is not to make
24- a concrete proposal, but rather to explore a problem space.
25- This space is complicated enough that agreement on vocabulary
26- is desired before defining a solution.
27-
28- Instead of objectives, we present a primary problem
29- and a set of subsidiary problems.
24+ > [ !NOTE]
25+ > This design document is preserved as part of a valuable conversation about
26+ > function composition, but it is not the basis for the design eventually
27+ > accepted.
3028
3129### Problem statement: defining resolved values
3230
@@ -838,7 +836,10 @@ so that functions can be passed the values they need.
838836It also needs to provide a mechanism for declaring
839837when functions can compose with each other.
840838
841- Other requirements:
839+ ### Guarantee portability
840+
841+ A message that has a valid result in one implementation
842+ should not result in an error in a different implementation.
842843
843844### Identify a set of use cases that must be supported
844845
@@ -975,26 +976,217 @@ Hence, revisiting the extensibility of the runtime model
975976now that the data model is settled
976977may result in a more workable solution.
977978
978- ## Proposed design and alternatives considered
979-
980- These sections are omitted from this document and will be added in
981- a future follow-up document,
982- given the length so far and need to agree on a common vocabulary.
983-
984- We expect that any proposed design
985- would fall into one of the following categories:
986-
987- 1 . Provide a general mechanism for custom function authors
988- to specify how functions compose with each other.
989- 1 . Specify composition rules for built-in functions,
990- but not in general, allowing custom functions
991- to cooperate in an _ ad hoc_ way.
992- 1 . Recommend a rich representation of resolved values
993- without specifying any constraints on how these values
994- are used.
995- (This is the approach in [ PR 645] ( https://github.com/unicode-org/message-format-wg/pull/645 ) .)
996- 1 . Restrict function composition for built-in functions
997- (in order to prevent unintuitive behavior).
979+ ## Alternatives to be considered
980+
981+ The goal of this section is to present a _ complete_ list of
982+ alternatives that may be considered by the working group.
983+
984+ Each alternative corresponds to a different concrete
985+ definition of "resolved value".
986+
987+ ## Introducing type names
988+
989+ It's useful to be able to refer to three types:
990+
991+ * ` InputType ` : This type encompasses strings, numbers, date/time values,
992+ all other possible implementation-specific types that input variables can be
993+ assigned to. The details are implementation-specific.
994+ * ` MessageValue ` : The "resolved value" type; see [ PR 728] ( https://github.com/unicode-org/message-format-wg/pull/728 ) .
995+ * ` ValueType ` : This type is the union of an ` InputType ` and a ` MessageValue ` .
996+
997+ It's tagged with a string tag so functions can do type checks.
998+
999+ ```
1000+ interface ValueType {
1001+ type(): string
1002+ value(): unknown
1003+ }
1004+ ```
1005+
1006+ ## Alternatives to consider
1007+
1008+ In lieu of the usual "Proposed design" and "Alternatives considered" sections,
1009+ we offer some alternatives already considered in separate discussions.
1010+
1011+ Because of our constraints, implementations are ** not required**
1012+ to use the ` MessageValue ` interface internally as described in
1013+ any of the sections.
1014+ The purpose of defining the interface is to guide implementors.
1015+ An implementation that uses different types internally
1016+ but allows the same observable behavior for composition
1017+ is compliant with the spec.
1018+
1019+ Five alternatives are presented:
1020+ 1 . Typed functions
1021+ 2 . Formatted value model
1022+ 3 . Preservation model
1023+ 4 . Allow both kinds of composition
1024+ 5 . Don't allow composition
1025+
1026+ ### Typed functions
1027+
1028+ Types are a way for users of a language
1029+ to reason about the kinds of data
1030+ that functions can operate on.
1031+ The most ambitious solution is to specify
1032+ a type system for MessageFormat functions.
1033+
1034+ In this solution, ` ValueType ` is not what is defined above,
1035+ but instead is the most general type
1036+ in a system of user-defined types.
1037+ (The internal definitions are omitted.)
1038+ Using the function registry,
1039+ each custom function could declare its own argument type
1040+ and result type.
1041+ This does not imply the existence of any static typechecking.
1042+
1043+ Example B1:
1044+ ```
1045+ .local $age = {$person :getAge}
1046+ .local $y = {$age :duration skeleton=yM}
1047+ .local $z = {$y :uppercase}
1048+ ```
1049+
1050+ In an informal notation,
1051+ the three custom functions in this example
1052+ have the following type signatures:
1053+
1054+ ```
1055+ getAge : Person -> Number
1056+ duration : Number -> String
1057+ uppercase : String -> String
1058+ ```
1059+
1060+ The [ function registry data model] ( https://github.com/unicode-org/message-format-wg/blob/main/spec/registry.md )
1061+ could be extended to define ` Number ` and ` String `
1062+ as subtypes of ` MessageValue ` .
1063+ A custom function author could use the custom
1064+ registry they define to define ` Person ` as
1065+ a subtype of ` MessageValue ` .
1066+
1067+ An optional static typechecking pass (linting)
1068+ would then detect any cases where functions are composed in a way that
1069+ doesn't make sense. The advantage of this approach is documentation.
1070+
1071+ ### Formatted value model (Composition operates on output)
1072+
1073+ To implement the "formatted value" model,
1074+ the ` MessageValue ` definition would look as in [ PR 728] ( https://github.com/unicode-org/message-format-wg/pull/728 ) , but without
1075+ the ` resolvedOptions() ` method:
1076+
1077+ ``` ts
1078+ interface MessageValue {
1079+ formatToString(): string
1080+ formatToX(): X // where X is an implementation-defined type
1081+ getValue(): ValueType
1082+ selectKeys(keys : string []): string []
1083+ }
1084+ ```
1085+
1086+ ` MessageValue ` is effectively a ` ValueType ` with methods.
1087+
1088+ Using this definition would make some of the use cases
1089+ impractical. For example, the result of Example A4
1090+ might be surprising. Also, Example 1.3 from
1091+ [ the dataflow composability design doc] ( https://github.com/unicode-org/message-format-wg/blob/main/exploration/dataflow-composability.md )
1092+ wouldn't work because options aren't preserved.
1093+
1094+ ### Preservation model (Composition can operate on input and options)
1095+
1096+ In the preservation model,
1097+ functions "pipeline" the input through multiple calls.
1098+
1099+ The ` ValueType ` definition is different:
1100+
1101+ ``` ts
1102+ interface ValueType {
1103+ type(): string
1104+ value(): InputType | MessageValue
1105+ }
1106+ ```
1107+
1108+ The resolved value interface would include both "input"
1109+ and "output" methods:
1110+
1111+ ``` ts
1112+ interface MessageValue {
1113+ formatToString(): string
1114+ formatToX(): X // where X is an implementation-defined type
1115+ getInput(): ValueType
1116+ getOutput(): ValueType
1117+ properties(): { [key : string ]: ValueType }
1118+ selectKeys(keys : string []): string []
1119+ }
1120+ ```
1121+
1122+ Compared to PR 728:
1123+ The ` resolvedOptions() ` method is renamed to ` properties ` .
1124+ Individual function implementations
1125+ choose which options to pass through into the resulting
1126+ ` MessageValue ` .
1127+
1128+ Instead of using ` unknown ` as the result type of ` getValue() ` ,
1129+ we use ` ValueType ` , mentioned previously.
1130+ Instead of using ` unknown ` as the value type for the
1131+ ` properties() ` object, we use ` ValueType ` ,
1132+ since options can also be full ` MessageValue ` s with their own options.
1133+ (The motivation for this is Example 1.3 from
1134+ [ the "dataflow composability" design doc] ( https://github.com/unicode-org/message-format-wg/blob/main/exploration/dataflow-composability.md ) .)
1135+
1136+ This solution allows functions to pipeline input,
1137+ operate on output, or both; as well as to examine
1138+ previously passed options. Any example from this
1139+ document can be implemented.
1140+
1141+ Without a mechanism for type signatures,
1142+ it may be hard for users to tell which combinations
1143+ of functions compose without errors,
1144+ and for implementors to document that information
1145+ for users.
1146+
1147+ ### Allow both kinds of composition (with different syntax)
1148+
1149+ By introducing new syntax, the same function could have
1150+ either "preservation" or "formatted value" behavior.
1151+
1152+ Consider (this suggestion is from Elango Cheran):
1153+
1154+ ```
1155+ .local $x = {$num :number maxFrac=2}
1156+ .pipeline $y = {$x :number maxFrac=5 padStart=3}
1157+ {{$x} {$y}}
1158+ ```
1159+
1160+ ` .pipeline ` would be a new keyword that acts like ` .local ` ,
1161+ except that if its expression has a function annotation,
1162+ the formatter would apply the "preservation model" semantics
1163+ to the function.
1164+
1165+ ### Don't allow composition for built-in functions
1166+
1167+ Another option is to define the built-in functions this way,
1168+ notionally:
1169+
1170+ ```
1171+ number : Number -> FormattedNumber
1172+ date : Date -> FormattedDate
1173+ ```
1174+
1175+ The ` MessageValue ` type would be defined the same way
1176+ as in the formatted value model.
1177+
1178+ The difference is that built-in functions
1179+ would not accept a "formatted result"
1180+ (would signal a runtime error in these cases).
1181+
1182+ As with the formatted value model, this restricts the
1183+ behavior of custom functions.
1184+
1185+ ### Non-alternative: Allow composition in some implementations
1186+
1187+ 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 ) .
1188+
1189+ This violates the portability requirement.
9981190
9991191## Acknowledgments
10001192
0 commit comments