Skip to content

Commit 3c87cb4

Browse files
committed
Edited for conciseness
1 parent 6664084 commit 3c87cb4

File tree

1 file changed

+40
-154
lines changed

1 file changed

+40
-154
lines changed

exploration/function-composition-part-1.md

Lines changed: 40 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -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
10391038
in a system of user-defined types.
10401039
Using the function registry,
10411040
each custom function could declare its own argument type
10421041
and result type.
1043-
10441042
This 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

10511044
Example 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
```
10621056
getAge : Person -> Number
10631057
duration : Number -> String
10641058
uppercase : String -> String
10651059
```
10661060

1067-
`Number` and `String` are assumed to be subtypes
1068-
of `MessageValue`. Thus,
1069-
10701061
The [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

10741068
An optional static typechecking pass (linting)
10751069
would 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
11551076
the `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
11691090
impractical.
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

11941097
The resolved value interface would include both "input"
11951098
and "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+
12081123
Without a mechanism for type signatures,
12091124
it may be hard for users to tell which combinations
12101125
of 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`,
12381145
except 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
12631156
date : 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

12801166
As with the formatted value model, this restricts the
12811167
behavior of custom functions.

0 commit comments

Comments
 (0)