4 ms·
Just to say it, for measurements that take a unit I am hardcore that you should include the unit in the variable time: `timeSeconds` `distanceMeters` `amount
by chasing 4y ago
Just to say it, for measurements that take a unit I am hardcore that you should include the unit in the variable time:
`timeSeconds`
`distanceMeters`
`amountDollars`
Have unit tests and everything, but also write your code so that when someone reads it they know as precisely as possible what's going on without other documentation.
- rtlfe 4y agoUsing types that encode the units, as suggested in OP, is strictly better.
- l0b0 4y agoYou're both right, but not all languages give you the choice. And a lot of types are implemented badly. For example as a subclass of a number class, so that `amountDollars + timeSeconds` and other nonsensical statements aren't errors.
- nyanpasu64 4y agoI'm not optimistic on unit types myself. I've found that unit-named variables often with transparent type aliases as documentation (type Sample = usize, type Amplitude = i16/f32) have many of the advantages of distinct units, without their downsides compared to bare numbers. In several projects where I've used naming and transparent aliases comprehensively, I don't recall ever letting a unit mistake escape from my local tree into master, since I reread my own code when committing and merging. In one case (https://gitlab.com/exotracker/exotracker-cpp/-/blob/dev/src/doc/timeline.h https://gitlab.com/exotracker/exotracker-cpp/-/blob/dev/src/... used to have two EXPLICIT_TYPEDEF) I did add distinct units because I found I was mixing together two types too often during development. Though I find that unit types come with significant disadvantages (ergonomic and semantic flaws), making them far from strictly better than bare numbers (much like Rust is far from strictly better than C/C++/Zig): - You need an implicit conversion to eg. size_t, otherwise you can't pass (smp: Sample) into array indexing like (amplitudes[smp]) or data slicing, without an extra conversion or accessing the underlying value like (smp.v). But you can't allow (smp += midi_pitch) to convert both arguments to int, then cast the result to Sample when assigning. - You need some conversion to allow (smp + 1) with type either integer (convertible to Sample) or Sample, unless you want to annotate all arithmetic with boilerplate like (smp + (Sample)1), or (smp.v + 1). I've experienced this problem in my own code, and had to write (smp.v) when my compiler saw (smp + 1) and told me it didn't know whether to wrap 1 or unwrap smp. - Expressions of type Amplitude * 2 should have type Amplitude. Go's time library gets this wrong, where multiplying Duration * Duration = Duration, which makes sense if Duration is an integer like i32 or i64, but not if Duration is a unit system dimension. - (not a regression but a limitation) Units won't stop you from adding two temperatures in Celsius. To fix this you need separate coordinate and displacement types, which is a new pile of complexity. - You may want distinct types for "samples/sec" and "cycles/sec". Modeling this in type systems has multiple current approaches, all of which rely on language support (F#) or complex type machinery I've had issues with. - You can't easily convert between slices of f32 (like an audio buffer provided by the OS), and slices of Amplitude<f32>. Or worse yet vectors of f32 and Amplitude<f32>. (This problem affects bulk data in collections, more than scalar types generally passed and returned in the stack.)
- hamandcheese 4y agoIf you're the type of moron who will write: chargeCustomer(amountDollars: valueInCents) I have little faith that types would help you. You could simply do: chargeCustomer(amount: Dollars.from_int(valueInCents)) And create the same bug. Positional arguments is just as big an evil as non-typed units, IMO.
- diarrhea 4y agoAt that point, why not have dedicated types and get rid of primitive ones? It shifts all that work to the compiler, whose job it is to excel at this kind of stuff (adding cents to penny types does the right thing automatically etc).
- mrkeen 4y agoBecause you still have to co-exist with other software beyond the type system's boundaries. The amounts are going to be converted to/from JSON, put in and out of databases, show up in logs etc. When it comes time to invoke Amount.fromCents(..) at the boundary of the system, it's nice if the front-end posts a variable called amountInCents.
- debaserab2 4y agoAgreed, but I can’t help but think that this is just a poor man’s typings for languages that don’t directly provide them.
- sigha887 4y agoWould you also add other type-related aspects into variable names? `firstNameString`, `ratioFloat`, `colorEnum`? These things should be types, not easy-to-ignore type-encodings into variable names..