I see the value in Stas’ proposal mainly as:
1) It moves the caching problem to the consumer.
If your app has a list of 100 items to be displayed using the same Message, but different args, you solve it above
FluentBundle is the lowest level, non-consumer-friendly, API that should allow higher level APIs such as one called currently
Localization or some bindings to do what they want.
With the current model, we basically enforce any optimizations of that type to requested out of the implementation of
2) It allows for separation of Message retrieval from formatting
Currently, those two actions are combined into a single operation, indivisible.
FluentBundle was meant to be used by consumers, I’d understand such API.
But since it is not, having ability to perform each operation separately has a value in composability and flexibility we offer to the higher level APIs.
Caching, listed above, is one, but I can imagine scenarios where one would want to retrieve a
Message and format it using multiple separate
I don’t think there’s any reason to disallow such operation, and Stas’ proposal seems to offer the right set of APIs to allow for such operations.
@eemeli’s suggestion to return an instance that has internal reference to the
bundle feels off to me. It’s more hidden magic that doesn’t seem to be necessary, and locks us down in multiple concepts that don’t hold.
Message in Fluent exists independently of any
Bundle. It’s actually part of the
Bundle on a data model level is just a list of references to
Resource objects plus a cache of intl formatters used by the bundle.
We could also just allow for
Message to be retrieved from
Resource, and then be formatted by the
3) It puts the error fallbacking in place
There are two classes of errors we’re discussing here: a) errors that prevent you from retrieving the
Message or value you want. b) errors that happen during
The former type is a clear breaking scenario and the main result of
FluentBundle.retrieveMessage(id) should be that it either succeeded and returns what you asked for, or failed with a single error.
This is the base of for a higher level API and bindings that do error fallbacking, and maybe also last-resort option of displaying
l10n-id in place of a message if that fails.
The latter is very different, because we want to resolve as much of the message as possible, while accumulating errors on the way and in the end always return some
string and a list of errors it accumulated.
Once again, the higher level API may decide what to do in case of errors and it may decide to fallback.
In particular, it’s perfectly possible for the same
Message to fail when resolved in the context of one
FluentBundle, but succeed when resolved in the context of another.
I’d like the low-level API to reflect that.
On the dislike side, Stas’ proposal adds abstraction allocation and logic.
I never saw the value of “pull” approach, but that’s likely because I see compound messages as tightly related to UI widgets and in such scenarios I’ve never seen a use case of retrieving some, but not all, attributes.
Stas’ also in several issues lists “performance characteristics” of different approaches as something that can change in time (his last comment in issue 364).
Since we talk about low-level generic Fluent API, I believe the common denominator should be algorithmic and cyclical complexity, not benchmark performance, especially not our API should not be designed based on snapshot performance of some JIT engine.
For that reason, I believe Rust and C++ are much better languages to consider when designing the API for all languages. If we need to allocate more, call more functions, hold more references and perform more operations, we’re moving away from a performant API, even if SpiderMonkey 69 happens to JIT away it better.