11:42
<eemeli>

Looking over and considering Jesse's upcoming Decimal/Measure presentation and explainer, I get the sense that we really ought to find a way to reach the end goals here in multiple smaller steps, rather than one big leap. By Decimal normalising trailing zeros, the use cases for them introduce a dependency on Measure, but it gets a bit hairy if Measure then also depends on Decimal for its value representation.

What if we were to initially not include any conversions in Measure? Then we wouldn't need to change its inputs at all, and we would still be providing a way for Intl.NumberFormat and Intl.MessageFormat to get their formattable inputs as a combined value+units+precision package.

Then we could consider separately the Smart Units part of this whole thing adding unit conversions, and Decimal defining its accessors on Measure, and we'd avoid having everythin depend on everything else.

12:11
<eemeli>
Here's a sketch of what that might look like: https://gist.github.com/eemeli/0bd413d2f711cbd6016673af8d68c38c That's in TS, so the real thing would need to have appropriate runtime checks for the types. The lack of any methods on the class is quite intentional, as is freezing it. I'm also starting to think that we might want to call this "Amount" rather than "Measure", mostly because then it also makes sense for currencies: it feels really clumsy to talk of a "measure of money", whereas an "amount of money" (or anything else, really) makes more sense.
12:17
<Jesse>
thanks!
12:18
<Jesse>
happy to discuss another name
12:19
<Jesse>
"measure" suggests physical quantities and (it feels like) is excludes currencies; "amount" clearly includes currencies but (to my mind) weakly excludes other kinds of measurements
12:22
<Jesse>
shouldn't we have a toLocaleString or at least toString in Amounts?
12:29
<Jesse>
I wonder if want to tie in to ISO 4217 (standardization of currencies) somehow or whether measure/amount is merely any tagged number (or string, or bigint) at all
12:30
<Jesse>
new Amount(42.75, "foobar gramz")
12:30
<Jesse>
I like the simplicity of the zero-method approach but I wonder if this deflates the value-add too much
12:31
<Jesse>
one of the nice value-add parts of measure, to my mind, was its tie-in to CLDR's units.xml
12:31
<Jesse>
exposing new capabilities to the language, rather than merely attaching a string to a number
12:43
<eemeli>
I would argue that e.g. "3 meters" or "42 grams" are both also "amounts", whereas calling those "measures" or "measurements" would be more opinionated about how the figure was reached.
12:44
<eemeli>
toLocaleString potentially yes, delegating to Intl.NumberFormat. Not at all so sure about toString; what would be the use case for it?
12:45
<eemeli>
I think that's a question that would be best answered within a more general currency formatting context, as any answer here ought to match the answer given by Intl.NumberFormat.
12:47
<eemeli>
I agree that conversion is nice, but I think it's also complicated -- and has its own Stage 1 proposal: https://github.com/tc39/proposal-smart-unit-preferences
12:57
<Jesse>
ah, right
12:57
<Jesse>
I wonder if the harmony under discussion should also include smart units
12:57
<Jesse>
the current measure README explicitly refers to smart units
13:03
<eemeli>

Yeah, there's a continuum of (at least) three separate considerations here that we're trying to solve:

  • How to represent a number together with its unit/currency/precision (Measure/Amount)
  • How to convert values between compatible units (Smart Units)
  • How to represent numbers better (Decimal)

I think Decimal depends on Amount, and unit conversion might depend on Decimal if that's the chosen type for the value of an Amount after conversion. So I'm looking for a way to solve the first problem in a way that doesn't require simultaneously solving all the problems and introducing all of these cross-dependencies.

13:09
<Jesse>
I like the suggestion to use decimal as the result of doing conversions
13:09
<Jesse>
these typically involve things like taking reciporocals or multiplying values (including squaring, cubing, etc.) so decimal feels like a good match
13:10
<Jesse>
since it's promise is to provide more precision
13:10
<eemeli>

The cleanest way that I see for this stack to proceed would be:

  1. Amount
  2. Decimal
  3. Unit conversions

Then each step would only depend on things before it.

13:12
<Jesse>
that looks good -- I'd like to see decimal building on amount/measure so I think it makes sense to focus on amount
13:42
<Jesse>
I think given the amount of interest in these topics it might make sense to set up a regular call for decimal, measure/amount, and smart units
13:43
<Jesse>
things are sort of scattered atm -- I've discussed this stuff in TG2, TG3, in plenary, in this channel, and (to a much lesser extent) the delegates channel
13:44
<Jesse>
how about tomorrow (Thursday, February 13th) at, say, 18:00 CET?
13:58
<littledan>
I'm glad that we're laying out the whole space, but let's see if we can keep the result, or at least the first step, not too complex.
14:00
<littledan>
I'm sold on the need for a unit'ed Measure class, but I don't know why we'd have a third class
14:01
<littledan>
or is Amount just a way to rename Measure?
14:01
<littledan>
sorry maybe this was just me skimming too fast and the proposal is already good :)
14:02
<Jesse>
I think the current thinking is that amount is just a renaming of measure
14:02
<Jesse>
plus a certain shrinking of its scope
14:04
<Jesse>
there was a discussion in various places about whether measure/amount should support arithmetic, at all, and the consensus seems to be "no"; the most recent sketch is of a class that has no methods at all (except possibly toLocaleString)
14:04
<littledan>
I can see the argument for arithmetic, but it's really complicated, and having the base type exist facilitates people writing libraries to do that. (Of course the same could be said for Temporal)
14:05
<sffc>
(I can join tomorrow at 18:00 CET, or earlier)
14:05
<littledan>
I'd expect (citation needed!) that keeping it to one numerical type is probably simpler implementation-wise, but 3 classes vs one class with multiple possibilities for the value's type doesn't really simplify anything.
14:05
<littledan>
Personally I don't mind Measure baking in Decimal (since measured quantities are very often decimals, and other numeric types might just be used in error)
14:08
<Jesse>
to my mind there are a couple senses of "arithmetic" -- 1. adding (or multiplying, or ...) two measure objects; and (2) converting to a "higher-order" unit, such as going from meter to meter^3 and converting between compatible scales (meters and inches)
14:08
<Jesse>
my understanding is that (1) is currently not very well received, but I'm not sure about (2)
14:09
<sffc>
Just looked at Jesse's slides. They look like exactly the kind of conversation I think we should be having right now. 👍️
14:10
<eemeli>
Yeah, "Amount" is effectively a synonym "Measure".
14:10
<Jesse>
Personally I don't mind Measure baking in Decimal (since measured quantities are very often decimals, and other numeric types might just be used in error)
agree, though I can see the value in rolling just with currently existing primitive types
14:11
<Jesse>
(one could go even further an imagine supporting only Number)
14:13
<eemeli>
As I see it, the primary utility gained from a minimal Amount/Measure is in the improvements of the Intl.NumberFormat API, and so it'd be rather suprising if new Amount('42') or new Amount(42) would not work.
14:13
<eemeli>
Given that calling nf.format('42') or nf.format(42) works.
14:14
<littledan>
Well, we could apply the new Decimal logic in the Measure constructor
14:14
<littledan>
for casting
14:15
<eemeli>
Huh, just realised as I typed that that new Amount(42) doesn't feel as wrong as new Measure(42) does, i.e. without the options.
14:15
<littledan>
(I like the name Measure)
14:16
<eemeli>
Why should new Amount(x).value === x not be retained? Something like new Amount(x).asDecimal() could be a thing, but there's no reason to throw away the original input value.
14:17
<Jesse>
Well, we could apply the new Decimal logic in the Measure constructor
I like that idea too but I've heard from a couple people that they'd like to use bigints in measures/amounts, and I'm a bit uncomfortable with going beyond the range of Decimal128
14:20
<Jesse>
I've made a new calendar item PR but it's not merged yet
14:20
<Jesse>
https://github.com/tc39/Reflector/issues/551
14:23
<eemeli>
I should be able to make that time as well.
14:24
<eemeli>
I'd appreciate a calendar invite at eemeli@mozilla.com for the meeting, once such is available.
14:37
<littledan>
why is this identity important?
14:45
<eemeli>
Because it would be surprising for that identity not to hold, and because it makes it straightforward to support all the same types that are supported by Intl.NumberFormat.
14:48
<eemeli>

I'm not so sure that it's important for the value type to be retained after e.g. conversion. So something like

new Amount(42, { unit: 'meter' }).convertTo('foot-and-inch').value

could well be a Decimal. Which is why I think we ought to do Amount first, then Decimal, then unit conversions. That way each step only depends on previous work.

14:51
<Jesse>
I think I'm favor having something like an .asDecimal method, to make any conversion explicit
14:54
<eemeli>
I added a .toLocaleString() to the gist.
14:58
<littledan>
I don't really feel like "this identity is intuitive" is a strong enough reason to make the data model more complex. I'd want to see a use case where you really want the representation to be something other than Decimal.
14:59
<littledan>
(yes, more flexible is more complex)
14:59
<littledan>
that only makes sense if the underlying data model is flexible among types
15:02
<Jesse>
I don't really feel like "this identity is intuitive" is a strong enough reason to make the data model more complex. I'd want to see a use case where you really want the representation to be something other than Decimal.
I've heard Mark want to use measure/amount to represent values of cryptocurrencies where one has a huge number of significant digits (beyond what can be faithfully represented in Decimal128)
15:03
<Jesse>
so I think for him being able to get the original value (in his case, a bigint) would be important
15:06
<littledan>
I don't understand why cryptocurrencies need more than 34 significant digits. Maybe they forgot that it's floating point rather than fixed point?
15:07
<littledan>
anyway sounds like an argument for BigDecimal over Decimal128; this is the wrong level to make that change IMO
15:07
<Jesse>
I also didn't push him on that; the cryptocurrencies I know don't use that many digits, but MM works in this space so I didn't push
15:08
<Jesse>
FWIW the Ethereum VM uses Decimal256
15:08
<Jesse>
so I guess they foresee a need for a vast number of digits
15:08
<littledan>
sure... idk I think this use case needs to be pushed on before we make a complexity-increasing decision on that basis
15:09
<Jesse>
right, I'm not familia with any other use case where such range is needed
15:09
<littledan>
you can always just not use this library
15:09
<littledan>
(and you can always fall back on Intl.NumberFormat taking strings)
15:12
<Jesse>
Decimal128 also seems to be the upper limit, today, of out-of-the-box compiler support, which is another indicator that 128 bits should be enough
15:13
<Jesse>
I like the argument that if 34 significant digits isn't enough, there are other ways to get what you want
15:13
<littledan>
I don't think compiler support is extremely relevant. This is more of a library thing. Anyway IEEE defines up to 128.
15:48
<eemeli>
If there's a need to do anything with the value, that's easier if it's a number, because then you can use + - * / etc. If my use case doesn't need the full 128 bits of precision, then a Decimal value would be clumsier to use.
15:49
<eemeli>
I would be open to removing the .value accessor, and including something like .asNumber().
15:50
<eemeli>
That would make adding a .asDecimal() later rather straightforward.
15:55
<littledan>
do you have an example of a case where that's a valid, rather than buggy, thing to do?
15:55
<littledan>
the design of Decimal is based on the hope that its methods won't be too clumsy to use in practice
15:56
<littledan>
I don't understand the argument for getting rid of an accessor for the value. IMO we should just decide on the data model and then represent it directly; no need to hide it.
16:06
<eemeli>

I see enforcing the Amount value to be a Decimal to be unnecessary complexity, because that's not needed when Amount is used when passing it between user code, or when passing it to Intl.NumberFormat. I would also like to not make Amount conditional on Decimal, because that seems unnecessary. The primary direct use case for it that I have in mind is Intl.NumberFormat, and for that it'd be fine if it was just an opaque blob.

It would be nice if it was also useful for passing between user code, but if that requires reaching consensus on Decimal, then I would much prefer introducing it as a part of that consensus.

16:09
<littledan>
OK, sounds like there aren't any particular use cases, and we just disagree on what "simpler" means
16:09
<littledan>
but I do think we can work these details out after getting consensus on the broader plan overall
16:11
<eemeli>
Are there any particular use cases that would explicitly benefit from having the value as a Decimal?
16:12
<littledan>
well, if given a Measure as a parameter, then you know which type you get out of .value, and you know that you're not changing the value when accessing it as a decimal. So it becomes easier to use reliably.
16:13
<eemeli>
Hang on, "changing the value"? The Amount/Measure values should be immutable, yes?
16:13
<littledan>
changing in the sense of, when I access the value as a decimal, I faithfully get the exact thing
16:13
<littledan>
it's not transformed during the access
16:14
<eemeli>
If that's a concern, then would it not also be a concern if the transformation happens in the constructor?
16:14
<littledan>
well, it's a question of the data model, but sure, my personal preference would be for it to be strict
16:15
<littledan>
anyway, I think this is not that major of a question
16:15
<littledan>
the major question is: are we happy with introducing two classes like this? does this resolve the needs of Measure and Decimal?
16:16
<sffc>
If we stick with Amount always being a decimal, then we could just say that new Amount(42) or new Amount(42n) are not allowed; you need to create one from a Decimal
16:17
<eemeli>
It seems really rather unergonomic to need to do new Amount(new Decimal(42)).
16:18
<littledan>
I'm really confused about what goals you're trying to achieve, Eemeli
16:19
<littledan>
is it an abstract sense of minimalism/good design, or is there anything more concrete that you're trying to ensure is enabled?
16:25
<eemeli>

I am primarily looking to improve the experience of using Intl.NumberFormat by better separating the "what" and the "how" of its inputs, where we currently require unit and currency to be defined in the constructor, while the numerical value is passed in in the .format() method.

This is particularly problematic in localization, where the options bag otherwise contains values that a translator may want or need to tweak. Including currency: 'EUR' there means that a translator working from French to US English could "fix" that option to say currency: 'USD' instead. Amount solves this problem by letting the developer fully control the value being formatted, by wrapping it in an object that includes both the currency code and the value.

16:26
<littledan>
OK, so then the problem with certain designs is that it'd make the upgrade of existing code more complicated, to insert decimal128 into the mix when the surrounding code already works?
16:29
<eemeli>
It would add extra, unnecessary work for an implementation to need to convert a value to Decimal before passing it on to Intl.NumberFormat, which already supports numbers, bigints, and numerical strings.
16:29
<littledan>
Alright, I see that argument
16:29
<eemeli>
And it adds complexity to our process to have two proposals each depend on the other.
16:30
<littledan>
I'm still not super excited about you saying that this means that Measure can come before Decimal -- I hope that Decimal can proceed once we work out the relationship with Measure (maybe Measure would come sooner, but I don't want to add artificial extra delay to Decimal)
16:33
<littledan>
anyway, I'm OK if we end up making Amount/Measure be polymorphic in this way -- not my first choice, but not terrible
17:16
<eemeli>

So here's an idle thought: If we end up with an opaque Amount that can hold a value + units + precision, and the units and precision are optional, then why should we add a separate Decimal class if we were to want to do operations like .add() or .multiply() on such values?

As in, wouldn't it be cleaner to consider unitless values as just a type of Amount, rather than needing its own special class?

17:18
<eemeli>
At least personally, I find amount.add(otherAmount) much less objectionable than decimal.add(otherDecimal), because in the former the values don't feel like they ought to be representable as primitives.
17:25
<Jesse>
one of the issues that has come up with arithmetic on numbers with precision is how to propagate the precision to the result
17:25
<Jesse>
IEEE 754 gives an answer to that, but there are other valid answers
17:27
<Jesse>
I guess the issue with rounding errors in JS Numbers (mainly arising once one starts doing arithmetic) would persist in this approach
17:28
<sffc>
I have a few arguments against the polymorphic Measure: 1. Solves confusion regarding how we record precision 2. More seamless i18n integration with the new number ecosystem we're building 3. Easier to implement I acknowledge that integrating polymorphic Amount into an existing code base is a lossless transformation. However, I've previously been convinced by Jesse that Decimal128 covers basically all use cases. So I don't find that argument for polymorphic Amount to be very strong.
17:34
<eemeli>
sffc: Could you clarify what you mean by "polymorphic Amount"? Is that an Amount with a value that can be one of multiple different types?
17:37
<Jesse>
I think of precision-less, unitless Amount as a sort of limiting case; I think of decimals as mathematical values
17:37
<Jesse>
and I guess developers would also think of them as such (though I admit that that's a bit hand wavy on my part)
17:39
<Jesse>
I can imagine an app that only works with integers under, say, 1 million. Using bigints for those numbers is fine, and helps me to think more clearly about my program than if I were to use Numbers
17:39
<Jesse>
(just making an analogy to existing datatypes)
17:40
<sffc>
sffc: Could you clarify what you mean by "polymorphic Amount"? Is that an Amount with a value that can be one of multiple different types?
Correct, as opposed to an Amount that is always Decimal
17:43
<eemeli>
This suggests to me that starting out with an opaque Amount would be safest, as it would allow its value to later be defined as a Decimal, or for the capabilities of Amount to later be expanded to cover what's proposed for Decimal.
17:45
<nicolo-ribaudo>
If we day "for now Amount is opaque basically containing a string / mathematical value", end then we re-explain it in terms of decimal, we have to be careful about how it might round differently
17:47
<eemeli>
We would need to initially impose limits in the Amount constructor that would be within the limits of what's expressible in IEEE 754.
17:47
<Jesse>
just to be precise, we mean the limits of IEEE 754 Decimal128?
17:48
<eemeli>
Yeah.
18:02
<littledan>
I'd like the discussion during the meeting to focus first on building consensus in committee that we should do both of these classes, and then later we can work out details like proposal merging vs not, and polymorphic vs decimal-linked measure. For this, it'd be ideal if all of us could hold back so that the queue could initially be others in committee who haven't already been discussing this as a group at length. (And then afterwards we can make statements about our positions within this, but I take it we all agree with this two-class approach where they have basically this role.) What do you think?
18:03
<littledan>
(I mean, not necessarily all of us, but the group who's been more active in discussion)
18:03
<littledan>
for example, ljharb has repeatedly expressed opposition for an unrelated reason. I think we should get that out of the way before we go into these additional details. (That topic is usually brought up just at the very end of the timebox, which doesn't allow for discussion)
18:04
<littledan>
but also we should explore other concerns the rest of the committee might have, e.g., "is this class worth it at all, or should it just be a library?"
18:04
<littledan>
I think we've made clear good reasons for all those basic points, but if people still disagree with them, we also won't get Stage 2, even if we have agreement among this group
18:06
<littledan>
sorry I don't mean to dictate the order, we can do whatever order, as long as we make space to hear out the various concerns. I just want to make sure that this conversation is broad and not only the same people who are in this channel and other meetings outside of plenary arguing among ourselves.
18:09
<Jesse>
I'd hope that, because the presentation is about measure, too, the set of those in committee who might have useful feedback would be somewhat wider
18:10
<Jesse>
and I hope others who have watched the development of related-but-distinct proposals might have some insight
20:09
<eemeli>

I take it we all agree with this two-class approach where they have basically this role.

I'm not sure that I agree with this part. I think the discussions so far have identified valid use cases that are served exclusively by Amount/Measure. Furthermore, I think that an opaque Amount can be defined and can be useful without any dependence on Decimal. Presuming then that at least one class (Amount) is added to the language, it is not clear to me that it would be better to add a second Decimal class, rather than including its functionality in Amount.

20:17
<eemeli>
I updated the gist again to show an opaque Amount, with a toString() method to get at a string representation of the value:
https://gist.github.com/eemeli/0bd413d2f711cbd6016673af8d68c38c
21:19
<littledan>
How does this relate to arithmetic operations?
21:19
<littledan>
I understand if Amount/Measure serves your use cases, but we've presented a bunch of others which are not just that.
21:20
<littledan>
the other thing is, I was imagining that Amount/Measure would represent exactly the value that's passed into it; it doesn't form a floating point exponent itself. And this is exactly what we're missing for decimal numbers.
21:22
<littledan>
Sometimes, a feature comes through which is useful for some people in committee rather than others. For example, most of TC39 is rather bored by Intl proposals, but they seem like a reasonable standard library, so they let them through. I'm wondering if Decimal might fall into that category for you, or whether it's something that you find to be important to block.
21:40
<eemeli>

I'm trying to say that as it seems like arithmetic operations can reasonably be considered to be included on Amount/Measure (they're currently included in its README, even), it's not clear to me how or why those arithmetic operations would differ from the arithmetic operations of a Decimal.

So if we were to do pretty much what you proposed earlier, and consider the value of an Amount to be the same as the opaque value of a Decimal, we could serve the use cases of Decimal with an Amount that had .add(), multiply() and the other Decimal methds on it.

21:42
<eemeli>
Essentially, I'm trying to ask why we would need a second class beyond Amount.
22:17
<littledan>
Part of the reason was to make Decimal simpler by following the IEEE 754 data model, rather than make up our own arithmetic that would work against various values that can be in Amount
22:25
<eemeli>
What's to prevent using that with Amount values?
22:25
<littledan>
well, you didn't want the data model to be decimal, you wanted it to be number or string. So then it's not clear how to define arithmetic operations, or even everyday things like numerical equality.
22:32
<eemeli>
I am by no means settled in my opinions, and they've clearly evolved during this discussion. I do still think that having an explicitly Decimal value within an Amount is unnecessarily complex, but having an Amount effectively be a Decimal seems like it would simplify things quite a bit.
22:40
<littledan>
well... you've raised analogous opinions before (when it was strings), and we've talked it through, and seem to continue to disagree. What are next steps?
22:41
<littledan>
I'm still interested in understanding, when you say "unnecessarily complex", whether this means "I don't feel like using it in my programs" or "it's essential that JavaScript doesn't have this complexity"
22:52
<nicolo-ribaudo>
Something that worries me about having a single class is that operations on units can get very complex. As long as it's just a conversion between "compatible" units everything is fine, but to support basic arithmetic you need to: - potentially throw on add/subtract for incompatible units - for multiplication and division, support fractions (e.g. kg*m/s)
22:53
<nicolo-ribaudo>
While with the split it's easier to say "this class has more metadata, while this one supports arithmetic"