It would've saved the Mars Climate Orbiter: https://en.wikipedia.org/wiki/Mars_Climate_Orbiter
> An investigation attributed the failure to a measurement mismatch between two measurement systems: SI units (metric) by NASA and US customary units by spacecraft builder Lockheed Martin.[3]
That was only the proximate cause, the ultimate cause was cultural. As complex systems and efforts run into problems, it is trivial to blame the unit conversion when they had been ignoring people for months who had concerns [0]
> ... ground controllers ignored a string of indications that something was seriously wrong with the craft's trajectory, over a period of weeks if not months. But managers demanded that worriers and doubters "prove something was wrong," even though classic and fundamental principles of mission safety should have demanded that they themselves, in the presence of significant doubts, properly "prove all is right" with the flight
Dropping units on the NASA side also was problematic but really culture was the cause of the actual crash.
[0] https://spectrum.ieee.org/why-the-mars-probe-went-off-course
That's technically true, but if NASA's API accepted arguments in Meters instead of int32_t (or whatever the equivalent was in the language they used), then it would've been instantly obvious that the controller code that Lockheed Martin wrote was using the wrong datatype.
Do we know how the code was called, was it linked in or was it via IPC (the latter seems most likely to me, and then the question is does the IPC framework support a rich type system)?
That was exactly what I was thinking of when I wrote that.
But also think of how many libc functions take multiple ints or multiple chars in various orders. You can get carried away with typing, i.e. by having a separate type for everything*. Still, imagine you’re writing, say, a hypothetical IDE device driver and had separate types for BlockNumber and ByteInBlock so that it’s impossible to transpose read(byte_offset,block) instead of read(block,byte_offset), even if those are really the same kind of numbers.
That kind of thing makes a gazillion bugs just vanish into the ether.
I sometimes think about a related issue: suppose you have a function whose n parameters have n different types. Should the programmer be required to provide those parameters in a specific order? There's no ambiguity.
There appears to be some tension between different conveniences you might afford yourself. If you have read(offset: offsetTypeForRead, address: addressTypeForRead), you can catch when someone accidentally passes an address where the offset should be and an offset where the address should be.
Or, you can say "hey, I'm always adding the offset to the address; it doesn't matter which one gets passed first" and relieve the programmer of needing to know the order in which two semantically distinct variables get passed to `read`.
But if you do provide that convenience -- and it's not unintuitive at all; there really is only one valid interpretation of a combination of an address and an offset, regardless of the order you mention them in -- you lose some of the safety that you wanted from the types. If your variables are declared correctly, everything is fine. If there's a mistake in declaring them, you'll wave through incorrect calls to `read` that would have been caught before.
Huh, that’s an interesting point, and I’d have to think on that. There are still plenty of cases where ordering would matter, like subtract(a,b), unless you go whole hog and define that like
but that seems redundant. There are still plenty of other cases where I could your idea being useful. Like I always forget whether (in Python) it’s json.dump(file, data) or dump(data, file). Ultimately, should it matter? I’m passing a file handle and an object, and it’s unambiguous how those two args relate to the task at hand.A bit clunky but more foolproof is something like:
ide_read ( &(struct ide_loc) { .byte_offset = 10, .block = 20 } )