Everything Should Be Typed: Scalar Types Are Not Enough
10 points by diktomat
10 points by diktomat
There's a code smell called primitive obsession, which is essentially what the author addresses.
A few more resources on the subject:
This is good advice and in fact I’ve been experimenting with these patterns in a personal Go project - it has certainly caught bugs at compile time and I think it’s something I’ll continue.
But this statement is not true:
In Go, you get method access for free since defined types inherit the method set of their underlying type:
In Go, new types start with an empty method set, and you can only get the original methods via embedding, which is mildly annoying because casts no longer work, and it’s harder to refactor a generic type to a more specialised type.
I definitely agree.
One example taken out of this hole I've been digging for years.
I have a maths library which defines tensors and their inner types are not primitives, but unit types.
So i have something like tensor<unit<float,ut::Meter>,2,2>.
Technically the unit type is more complicated since I autodeduct the resulting unit as well.
Addition / Subtraction matching and multiplication and division unit calculations.
You basically have your parallel algebraic group of units.
The tensor is aliased into scalar/vector/matrix for simpler ops, but generally I use tensors.
The nice thing with that is you can auto reduce inner unit types if their exponent becomes 0 and you can also print based on the units base description.
This might sound convoluted, but I'm operating on different length types and caught wrong calculations with that.
Edit: Privately I'm also doing this to calculate kWh Heat requirements, PV generation estimates, etc.
The templates errors look like eigen3 ones, but that's a thing I'm willing to accept.
I agree with the recommendation to wrap primitive types like integers or strings in newtypes to make the code self-documenting and prevent bugs.
However I dislike the writing style and the grand claims made.
Stop using scalar types for domain concepts. Wrap every meaningful value in its own type.
The first example given in Rust is struct ShopId(String);, the built-in type String is wrapped in a struct, creating a new type. This is still a scalar type, because it contains a single value, it is not a composite data type.
In Go, you get method access for free since defined types inherit the method set of their underlying type
There is already another comment saying that this isn't true (I don't use go myself, so I don't know). If a language does that, it reduces the confidence you gain from using newtypes: mixing up variables becomes easy again if the value is casted to the underlying primitive without any explicit syntax. (C++ takes this a step further with single-argument implicit constructors.)