-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate operators from unit relations defined in JSON #1329
Conversation
@angularsen What's your opinion about this? Is this a worthwhile endeavor in the right direction? |
Apologies, I thought this was still being worked on so I haven't looked at it yet. Let me read more through the code and get back to you 👍 Will try to find time the next couple of days. |
No worries, I should have been more clear that this is a proposal in need of feedback. God jul! 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks very promising and a big improvement on the existing approach.
A few ideas below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks solid! This is some impressive work 👏
Some minor suggestions for you to consider.
The biggest one for me is to get some peace of mind that we are not breaking anyone as it is hard to verify by review.
I added a suggestion to maybe use reflection to compare before/after of all quantities and their public members, by outputting to text and diffing that.
@@ -119,7 +112,7 @@ private static QuantityRelation ParseRelation(string relationString, IReadOnlyDi | |||
{ | |||
var segments = relationString.Split(' '); | |||
|
|||
if (segments.Length != 5 || segments[1] != "=") | |||
if (segments is not [_, "=", _, "*" or "/", _]) | |||
{ | |||
throw new Exception($"Invalid relation string: {relationString}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exception can give an example of a valid format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could, but isn't UnitRelations.json
full of valid examples?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely, but I still think it improves the developer experience a bit.
var relationStrings = JsonConvert.DeserializeObject<List<string>>(text) ?? []; | ||
relationStrings.Sort(); | ||
|
||
var parsedRelations = relationStrings.Select(relationString => ParseRelation(relationString, quantities)).ToList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we throw on duplicates here, with a helpful explanation?
It probably gives compile errors in the generated code anyway, but it would be helpful if codegen failed early on invalid input to make it easier for contributors to find out what they did wrong.
If we eventually do #1354 , then duplicates could also occur implicitly by one explicit definition conflicting with the implicit definition for division operators.
Another option is to remove duplicates with a HashSet or similar, then fix the document when writing it back. But it may be complicated to know what to remove, in particular with the magic division operators, so it is probably better to just throw and have the author fix their mistake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I added a check after all relations are inferred.
Duplicate definitions in UnitRelations.json
are automatically removed with a SortedSet
.
/// "double" can be used as a unitless operand. | ||
/// "1" can be used as the left operand to define inverse relations. | ||
/// </summary> | ||
/// <param name="rootDir">Repository root directory.</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few examples with some actual quantities and units in a <example></example>
tag could be helpful for the reader
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
var @operator = segments[3]; | ||
var left = segments[2].Split('.'); | ||
var right = segments[4].Split('.'); | ||
var result = segments[0].Split('.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine, I was just thinking maybe regex was a good fit for this parsing?
Not important at all, just a tip to consider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that but it wasn't a big improvement, the regex is quite ugly and less comprehensible than the list pattern:
^(\w+)\.?(\w*) = (\w+)\.?(\w*) (\*|\/) (\w+)\.?(\w*)$
[_, "=", _, "*" or "/", _]
@@ -17,6 +17,8 @@ internal class QuantityGenerator : GeneratorBase | |||
private readonly string _valueType; | |||
private readonly Unit _baseUnit; | |||
|
|||
private readonly string[] _decimalTypes = { "BitRate", "Information", "Power" }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is not too much hassle, I would rather take a list of all quantities in the ctor to determine the value type from their Quantity
type, or a list of all decimal quantities determined from the original list of quantities.
We are not likely to get more decimal
types anytime soon, rather changing them to double
I think, but it may take some time and this is one less place to maintain when that changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wasn't needed anymore since I reworked the codegen, so I removed it 👍
var rightParameter = relation.RightQuantity.Name.ToCamelCase(); | ||
var rightConversionProperty = relation.RightUnit.PluralName; | ||
|
||
if (relation.LeftQuantity.Name == "TimeSpan") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe reuse some constants for all these repeating strings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to tidy up a bit, but code generation is always a bit messy and throwing around consts called _double
and _value
didn't make things better 😅
In [#1329](#1329 (comment)) this proposal came up: > Another idea: generate division operators based on multiplication. Right now we define: > ``` > ElectricPotential.Volt = ElectricCurrent.Ampere * ElectricResistance.Ohm (and generate the reverse) > ElectricCurrent.Ampere = ElectricPotential.Volt / ElectricResistance.Ohm > ElectricResistance.Ohm = ElectricPotential.Volt / ElectricCurrent.Ampere > ``` > But those last two could also be generated based on the first. This PR is an experiment implementing this. ### Breaking changes: - `TimeSpan = Volume / VolumeFlow` => `Duration = Volume / VolumeFlow`
Related #1200
In the PR adding generic math (#1164) @AndreasLeeb states:
I decided to give this a shot.
UnitRelations.json
contains relations extracted from the existing *.extra.cs files. I decided on a new file because multiplication is commutative and I didn't want to duplicate these in the individual quantity JSON files, or risk missing one or the other, so it's best to define them once in one place. The generator handles this by generating two operators for a single multiplication relation.The relations format uses the quantities method names. This is a bit unfortunate, but it's the best I could come up with without making the CodeGen project depend on UnitsNet, which would create a bit of a chicken/egg problem. This is not unheard of (self-hosted compilers) but I wanted to keep it simple for now.
The generated code enables the removal of 44 *.extra.cs files, and the 17 remaining contain much less code.