Unit from Unit#2628
Conversation
|
Thanks Carl! This definitely makes sense. I'll review your PR within a few days. |
gwhitney
left a comment
There was a problem hiding this comment.
This initiative makes sense to me as well, so I've taken the liberty of doing a code review. In fact, I don't actually see why the Unit constructor should take a string at all in the second position -- it needs to know the units, not something like 'blarf'. I think it would simplify the code to only take a Unit (or undefined) in that position. (And then the math.unit() function` deals with conveniences like parsing the second argument if it's a string.) So my code review has material from that perspective, along with comments that are important in either case. I will raise the point of whether disallowing a string in that second position would be considered a breaking change that should perhaps be incorporated in V11 in the issue for that (#2476).
|
Thanks for the comments! The approach of removing string as an option for the second argument in the constructor makes sense to me. I'll update the PR in that direction. |
|
Ok that approach makes sense to the two of us but @josdejong has to approve and then it has to be determined whether that's a breaking change that we're going to try to stuff into v11, currently due to be published tomorrow. So it would be great for you to prepare that as long as you're comfortable that it might have to be rolled back or redone if Jos prefers a different approach. If you want to be sure not to make effort that won't be used, you should wait for Jos's thoughts. I am a maintainer but Jos is the founder and architect. |
|
@gwhitney yes it makes sense to me to see if we can simplify the constructor, it's a bit cluttered indeed and there are more ways to do the same thing than probably needed. So, before this PR, you could create a unit in the following ways: new Unit(null, 'cm') // create a valueless unit "cm"
Unit.parse('cm') // create a valueless unit "cm"
new Unit(5, 'cm') // create a unit "5 cm"
Unit.parse('5 cm') // create a unit "5 cm"I understand the latest proposal is to replace the second argument to only accept a Unit: new Unit(null, Unit.parse('cm')) // create a valueless unit "cm"
Unit.parse('cm') // create a valueless unit "cm"
new Unit(5, Unit.parse('cm')) // create a unit "5 cm"
Unit.parse('5 cm') // create a unit "5 cm"Needing a const u = Unit.parse(name)
this.units = u.units
this.dimensions = u.dimensionsShows though what we actually need under the hood: So, how about changing the constructor to: function Unit (value, units, dimensions) {
// ...
}What do you think? EDIT: that makes the constructor less usable in general, but more flexible. For practical use cases you'll need a utility function like |
|
I definitely like the idea of making the Unit constructor the most semantically straightforward thing -- the "natural" way to create a unit. On the other hand, the vector of units and dimensions feels more like the internal representation than the natural specification of a unit. For example, unless I am mistaken, the vector of units and the dimensions have redundant information. I believe the dimensions vector can be determined by the vector of unit objects (add up the powers of each base_unit represented in the vector of units). So it would seem that for constructing, at least you would/should only need to specify the vector of units. My second concern is that the vector of units is in a pretty opaque form: it is an array of objects with keys 'unit', 'prefix', and 'power'; the values for the 'unit' key is an item from the Unit.UNITS object, the 'prefix' is an item from the 'prefixes' property of the 'unit', and 'power' is an integer. (I don't think a square root of an area unit like an acre is allowed.. ;-) In short, even just the vector of units might be a bit too "internals"-ish and cumbersome as a constructor. To actually use it directly, for 3 meters^2 per millisecond, you'd need to write something like: which is a bit of a mouthful. Maybe that's OK though, or would it maybe be better to have one inclusive library of prefixes and allow something like: (where the constructor uses a conventional order of: optional prefix, then unit, then power -- to avoid all those keys, and it can do the work to error-check that the prefixes are actually compatible with the units (i.e. no 'gigaft') and assemble it into its convenient internal rep, reconstructing the dimensions property?) In a slightly related point -- I hesitate to even bring it up at this juncture, so feel free to just completely ignore this question if it doesn't seem reasonable to contemplate at the moment, or seems ridiculous -- would the structure/code/usage here actually be cleaner/simpler if ValuelessUnit were its own class (probably one could find a better name) and then a Unit were simply a pair of a numeric entity and a ValuelessUnit? [I'd be tempted to call them a Unit (which would be the valueless one) and a Quantity (= a numeric value with a Unit), personally.] The points here are that one can delegate the details of specifying a ValuelessUnit to that class (whatever it's called), and then the UnitWithValue constructor takes the quite natural (value, valuelessunit) pair without that smacking of a weird recursion, and it would remove the slightly odd ambiguity as to whether a given Unit instance represents an actual quantity or just a way of measuring such quantities, i.e. in grams. I feel like that current ambiguity leads to a lot of branching in the code that might be unnecessary if the two classes were separated. Anyhow, just some thoughts given that the can of worms is at least slightly open; I definitely trust that you will steer a reasonable course here, especially in light of the thought to split off Unit from mathjs, which you probably know more about the state of than we do. |
|
It sounds like some good ideas for discussion. I especially like the distinction between Unit and Quantity! |
|
Thanks for your inputs Glen. Yes I have the same feeling: it will not help opening up too much "internals". In the end I hope we can integrate https://github.com/ericman314/UnitMath here, which as a very neat and simple API already. An API like Part of the problem is indeed that we now try to represent two different things in the same class: a unit with value and a valueless unit. Great idea to name them I've been thinking about whether it's worth to make a breaking change in the existing constructor API for this. At this point, I have the feeling we shouldn't over-engineer this. The idea of the PR was to add one more handy way to construct a Can you guys agree to leave the existing behavior as is for the time being? |
|
I've implemented the suggestions from @gwhitney which don't break anything. The |
|
Sorry for being late to the discussion. I would love to get UnitMath ready. It's based off of If you would like to help, the place to start is the v1 branch of UnitMath. That has all of the latest changes I and a few others have been working on. If you have time, please check it out. We can definitely implement the Unit from Unit feature there as well. |
|
Well, something in the new constructor has borked Unit.multiply. @josdejong, given that the decision is to leave the Unit(value, string) constructor for now, I suggest not waiting to release v11. That way @costerwi can fix this when feasible and merging this won't be a breaking change, so can be in v11.1. I left my review comments alone even though they are moot, as a record of thoughts for the Unit api. But if you want to mark them all resolved instead, go ahead, or I can if you want. |
|
Thanks for your update @ericman314!
Yes, I'll publish v11 now, and we can finish and merge this PR afterwards. |
Avoids typed problems in Unit._normalize
|
See the comment I just added; you'll need to restore the removed unit test before this can move forward. Thanks for your patience with this sort of thing, it's much appreciated. |
|
I don't see anything other than the last comment I put in the code; am happy to merge as soon as we reach consensus on that and resolve it. |
This reverts commit 286079d.
|
Just wanted to apologize, especially to @costerwi, about urging to remove the test when in fact the conclusion is it should be there. I will be happy to restore the test and merge the PR. Thanks for everyone's efforts! |
Actually we are going to do that test. Apologies to @costerwi and @ericman314. This reverts commit 9d4244c.
|
No need to apologize. This kind of collaboration is how the software becomes the best it can be. |
|
No problem @gwhitney, I appreciate your careful review and discussion! Thank you all for your contributions to this very useful project. |
|
Thanks guys 👍 |
|
Published now in |
Adding the ability to construct a new Unit using specified value and Unit, not just value and string.
This implementation matches the syntax of Unit.to(Unit | string) and makes the following use case much more efficient:
The best alternative I've found currently uses Unit.formatUnits() to convert Unit to string and then immediately parse that string back to a Unit within the constructor which seems very inefficient:
Thanks for consideration!