Open AnthonyDGreen opened 7 years ago
If you use a single back tick, how would compound units work (e.g. m/s)?
Well we'd definitely want them to look like that m/s. I'm not sure there's any ambiguity. 9.8m / 1
s = 9.8`m/s but I honestly haven't thought through a lot of examples to be sure. I wanted to put the idea of units of measure out there to see if others think it would help make programs safer. Do you see mistakes with unit conversions?
TBH, when I posted, I had just briefly looked at the F# docs and wondered why they used a pair of angle brackets to delimit instead of single character like here, since as you say, you can decompose compound units into multiple operations involving their component units.
There are two ways such a feature could work,
The first case would be to handle units as variable annotations, which the type and the control flow checker use to find errors. At runtime, any notion of units would vanish, which means: no guaranteed checks at runtime. It would also be strictly a language feature, that would be hard to extend.
The second possibility is a unit-library, which then needs a couple of language features to work as advertized. I've written an Unit-implementation myself, using a Unit-basetype, a couple of different intermediate types to separate length, weight, etc., and which also form base types for other units (f.e. mile inherits inch, the base length type). Every intermediate class is the host of operators, so every length gets add up as inch and needs to be properly converted.
To lower the bar a little, a simple feature would be required, which would be handy anyways: Arbitrary operators; in this case the "'in"; which is then expressed as:
Public Shared Operator 'in(value As Double) As Inch
Public Shared Operator 'mi(value As Double) As Mile
Public Shared Operator 'mi(value As Inch) As Yard
Public Shared Operator 'mi(value As Inch) As Mile
a.s.o.
If all operations are only in the inch-unit, it would work as follows:
Initialization
Dim l = 5.62'mi
Pow (returns square inch)
Dim q = l ^ 2
Simple conversion of mile into yard
Dim i = l'yd
Conversion of math result into nautical mile, which would otherwise be inch
Dim t = (i + l)'nm
Btw, currency is a corner case, because its conversions are not fixed, but get calculated daily / continuously. Another such case is Pixel, where knowledge of Dpi is required to convert to Inch.
@McZosch I don't think postfix unary operators are supported by VB.net
Just to add to the conversion conversation, in F#, a unit of measure can be applied to a value of any signed integer or floating-point type. You could, I think, set up meter
as a unit of measure and have some code using integer lengths in meters and other code using floating-point lengths in meters. How would this affect conversions, particularly ones without a nice integer conversion factor, like between feet and meters? Should it be possible to set up multiple conversions overloaded based on the underlying type, or should a unit conversion, for example, convert the underlying value to Double
, run the actual unit conversion calculation, then convert the result back to the original underlying type?
Syntax-wise, could something generic-like work, since the built-in integer and floating-point types are not generic?
Function CalcRectangleArea(width As Integer(Of in), height As Integer(Of in)) As Integer(Of in ^ 2)
Return width * height
End Function
Dim area = CalcRectangleArea(6(Of in), 2(Of ft))
'More complex example
Function CalcGravityForce(mass1 As Double(Of kg),
mass2 As Double(Of kg),
distance As Double(Of m)) As Double(Of N)
Dim G = 6.674E-11(Of N * m ^ 2 / kg ^ 2) 'Gravitational constant
Return G * mass1 * mass2 / distance ^ 2
End Function
Dim force As Double(Of N) = CalcGravityForce(1.2(Of kg), 1.3(Of kg), 2.5(Of m))
@AdamSpeight2008 Don't know, if having postfix operators is a huge problem. Any math operator to a degree is a postfix to its first operand.
Maybe the model could be derived to have a general conversion or as
operator, which is a rewrite of the normal CType-logic. This way, we could write:
Dim l = 5.62 as mi
which would do the same as
Dim l = CType(5.62, mi)
but is much more readable.
Don't like it. Would rather see a set of classes/structs as in keeping with timespan, rect or point etc If you have a struct Distance, you could set/get the length in different supported units, .Metres, .Kilometres, .Feet, .Inches, etc, just like you can refer to a TimeSpan TotalSeconds. And you could have an Area type that takes two Distances, and a Volume type that takes three Distances. Likewsie and Angle type that has Degrees, Radians, and Grad. And so on.
I do not want them baked into the language, especially not currencies. Have had too many problems with DateTime's in the past. And imagine what happens if a currency is renamed, a country divides ?
@AnthonyDGreen I like the concept of Unit Of Measure but not the proposed syntax, see alternative syntax #200
@AnthonyDGreen @AdamSpeight2008 ...just passing through here, haven't read all the comments above, but wanted to share how I am handling this right now:
<Extension>
Public Function KB(ArgValue As Integer) As Integer
Return Math.Abs(ArgValue) * 1024
End Function
<Extension>
Public Function MB(ArgValue As Integer) As Integer
Return Math.Abs(ArgValue) * 1024 * 1024
End Function
The above extension methods allow me to write code like this (thanks to VB's ability to invoke methods without requiring brackets):
Dim PageSize = 4.KB
Dim PagesPerMB = 1.MB \ PageSize
It seems possible that we could implement units of measure in VB as it stands today by using a library of specific types of measures (e.g. length type, weight type, etc), along with operator overloading and extension methods.
Food for thought!
@ericmutta That could be way to produce a unit of measure, but returning an Integer
defeats the point. You lose the contextual significance of the unit of measure.
It could be a implement as set of classes (see #200), but would it be good to have the compiler implement them for you.
This seems like it could be the kind of near-domain-specific feature that would benefit VB developers. This could useful to a host of practical applications. F# really sells the feature well here so hopefully we could borrow their design mostly intact (wouldn't it be even better if we could use F# libraries as-is?).
Right now, the syntax that seems the least ambiguous is the backtick:
Obviously this is awesome for units of length and weight:
This might also be awesome for units of currency:
And maybe even other less concrete things like flavoring primitive types:
Things like counts vs indexes, localized vs non-localized strings, byte encodings, etc can be represented an enforced by units of measure rather than Hungarian notation (no offense to our Hungarian sisters and brothers--we love you Hungary! <3)
I don't have a particular syntax for defining a unit in mind.
One thing I've been pondering is how this feature gels with VBs loose type conversion semantics. I've decided that even with
Option Strict Off
the feature isn't wasted because:1) impossible conversions will still give errors 2) we could special case it if we wanted since it's a new thing 3) a big benefit of the the feature isn't so much forcing you to write out conversions but ensuring that arithmetic is done correctly for the units involved. Put another way, it doesn't matter if the compiler converts inches to millimeters before adding, it matters that the conversion happen before adding. Even if it happens implicitly you're still getting a numerically correct result. 4) Since often conversions in both directions would be valid it might just be an ambiguity for the compiler to choose anyway.