3 ms·
Very few people write code whose output purely depends on the choice of types involved. Somebody, somewhere, has to make the choice of placing max/min/wrapping_
by hansvm 3y ago
Very few people write code whose output purely depends on the choice of types involved. Somebody, somewhere, has to make the choice of placing max/min/wrapping_add/wrapping_sub/wrapping_mul/... in the (u32,u32)->u32 shaped hole in your program, and placing the wrong one is probably a bug. Any caller expecting an arbitrary (u32,u32)->u32 recieving your changes shouldn't break, but not all callers have correct behavior for truly arbitrary functional dependencies.
From the other side, people picked your function in particular not because it fills that type hole but because it does something in particular. If you meaningfully change the thing it does then that probably breaks the callers.
The author's argument is something along the lines of (1) if you're changing the type signature, that's probably because a new implementation requires it, and (2) if the new implementation is sufficiently different to warrant a change in the type signature then it's going to break somebody. I don't particularly like the example since in my experience somebody adding a nullable input will leave the old behavior alone and just wants to call the same function somewhere else, so it probably actually is a non-breaking change for all existing callers, but it's not hard to imagine stronger examples where the author would be right.
- camgunz 3y ago"Breaking change" means it changes existing behavior. As in I used to get 'banana' when I passed 'b' and now I get 'bicycle'. If I still get 'banana', you didn't break anything.
- hansvm 3y agoIt sounds like we agree.
- layer8 3y agoNo, a breaking change is if the interface contract changes in a non-backward compatible way. If the interface contract never promised you to get “banana”, and you still relied on that behavior, then yes your client code may break, but that’s on your side of the program, and your fault for relying on unwarranted assumptions, and not a breaking change in the library you called. The purpose of interface contracts is exactly to precisely specify what can change and what can’t change without breaking clients, and conversely which assumptions clients can or can’t make. This is a precise give and take. The clients are expected to play by those rules just like the implementations of the interface are expected to maintain their promises — nothing more, nothing less.
- camgunz 3y agoThis is an academic definition. In a system with dependencies, if you change behavior your dependents relied on, you broke them. If you didn't, you didn't. In other words, if you break prod, "but we never promised to behave this way" is a poor excuse. When changing something, it's your responsibility to check for breakage, not just throw up your hands and say "not our fault" if it occurs.
- kazinator 3y agoE.g. a number of years ago now someone in Glibc development thought it would be fine to change the behavior of memcpy for overlapping objects. Nobody does that, right? It's undefined behavior. Downstream distros broke; it had to be backpedaled out.
- fiddlerwoaroof 3y agoTo be clear, nothing I said has much to do with types: preconditions and postconditions are a more general notion and types are one way to approximate them in such a way that they can be automatically checked. However, because of the halting problem, there will always be correct programs that cannot be typed.