"Spec-ulation" - https://www.youtube.com/watch?v=oyLBGkS5ICk
Which I fully agree with, basically there are no major, minor, patch level changes, in reality every change is a possible breaking changes, because we don't control what downstream is doing with our software.
I noticed this when recently writing a spec that defines backwards-compatibility. There's surprisingly few things you can do when you want to stay backwards-compatible.
With full backward compatibility, you agglomerate features, and changes are implemented as optional, with the default being the old behavior.
You can add layers that depend on existing code, but you cannot make existing code depend on those new layers.
/me runs ...
https://news.ycombinator.com/item?id=15602658
https://news.ycombinator.com/item?id=15593069
My comment was intended to be a wry one referencing those very interesting discussions and hinting that if you have types then you can more easily make breaking changes internally that aren't visible externally.
Who's "we"? I'm talking about changing the implementation.
> Did you watch the video segment?
I watched the bit where he said if you add a constructor to a sum type you have to change everywhere it's pattern matched. True. I'd love to see Clojure's solution where you don't have to change any existing code!
EDIT: Oh wait, I think it's even worse than that. I think he's talking about product types. In which case you should use projection functions and indeed you need to make no changes to old code!
1 - Not all changes can be kept internal, for example adding a new case to an ADT
2 - Internal changes can still alter the semantic of the API, even if from the outside everything still looks the same from the type system point of view
It is "possible" that my blowing my nose will disturb butterflies in China.
Internal to the organization that produces the software, they are highly meaningful. Extended to user base that reads the documentation, it remains equally meaningful.
Downstream that resists information and can not be controlled is on its own. Good luck.
There should be a mechanism for releasing breaking new major versions. And it is this:
1) Release a new version for all your DIRECT dependents. Ideally they should be subscribed to your updates.
2) After they have had ample time to test it, they will release an update for THEIR direct dependents.
At no time should C update A if C depends on B and B depends on A. In fact, ideally, if C depends on B1, ..., Bn then C will NOT upgrade A until all B's have upgraded. C should push the B's to do it and supply patches.
This means version pinning for all dependencies, my friends!!
The alternative is "dependency hell" or making multiple copies of A to live inside each B.
If some B's are taking their sweet time then it may be better for some C to submit a patch to B's repo, which even if not accepted can be pulled by other C's. The question is who "owns" or "maintains" a B project if they don't react to changes of some of their dependencies. The answer, I think, should be - fork the B if it's not being actively maintained.
Edit: I think HN needs a "reason for downvote" field, perhaps seen only by the person being downvoted. As it is, many downvotes convey very little useful information.
It matters less, IMHO, the further up the stack you get.
Being right at the bottom of every stack it matters most of all for the kernel, which is why Linus needs to be such a stickler. The amount of damage you can do by breaking userspace is vastly greater than the damage you do removing an hg command.
http://www.oracle.com/technetwork/java/javase/jdk-relnotes-i...
Of course, they are never at the level of a Python 3 release.
How does Linux manage to never break its API? For example in the event of a refactoring.
If you look closely at the kernel code it is easy to find two or more syscalls that have names and behaviors that are very similar, but where one is recommended above all the others.This is because the older syscall(s) were found to be flawed.
But rather than break existing software, they introduce a new syscall that fixes said flaws and leave the old one intact and recommend that in the future software use the new syscall.
It seems to me a lot of it boils down to "who gets impacted". If I have a bug in my kernel and a lot of apps are somehow relying on it, I need to be mindful of that and perhaps swallow the purity of the contract in order to provide a pragmatic fix.
Developer can still have that freedom in most[1] situations. When you have a new idea for a better/faster implementation that is, unfortunately, incompatible with the existing interface, you can usually provide compatibility with a shim that implements the old interface using the new interface.
Sometimes this is little more than some #define renames and a handful of very-thin wrapper functions. Occasionally it will be a bit heavier, which might make the legacy interface slower, but this should be balanced (approximately) the faster underlying implementation. If the compatibility shim is significantly slower (or has other unavoidable runtime issues), an upgrade that is slower for legacy uses that still works is always better than breaking existing code.
For an example and opinionated discussion, see JWZ's post[2] about his OpenGL 1.3 -> OpenGL ES 1.1 header.
[1] Exceptions are cases where there is a *fundamentally unavoidable cost to providing the interface (e.g. where N is the total number of interface entry points, something in the critical path unavoidably MUST take >O(N) time or space, or N is limited externally to a small value ("we only have silicon for at most N instructions"))
edit: [1b] Another exception would be when the legacy interface itself is a serious security problem. If your legacy interface unfortunately forgot to include a way to supply credentials and simply accepts writes from anyone, a prompt fix that breaks that API is necessary and appropriate. Next time you're designing an interface, be sure to remember that security has to be baked in from the very beginning!
[2] https://www.jwz.org/blog/2012/06/i-have-ported-xscreensaver-... (you might want to copy/paste that URL if your browser betrays where you came from with a Referer header)
That's more or less the thesis of Steve's blog post too.
In these cases, a Zero Regressions policy is a bit silly. You can flip things around and look at the new code as baseline and from that perspective the old code has many more regressions relative to it.
There seems to be a pretty stark divide between these mindsets.
That way the app would be "backward compatible certified" if it used this layer. The layer would only run during deployment.
I guess an approximation is the Passing indicator on Travis or GitHub.
https://killercup.github.io/bitrust/
This explains the Rust approach:
They literally run all tests against every crate published to the package ecosystem. That is amazing.
But the idea is far from it. Commercial compilers probably had this more than open source ones in the past. Though, oddly, it was probably a weakness in some respects, since it almost certainly slowed feature development/releases.
It's a spectrum:
Rust: recompile the world when you update the compiler
C++: recompile the world when you change ABIs or when the ABI changes (My understanding is that MSVC++ changes the ABI every ~2 years? I could be wrong.)
C: never recompile the world
It's not that we don't want the benefits of a stable ABI, but it's a monumental task, and there are more important things for now.
Don't forget that you can get a stable ABI today by using the C ABI. You just lose out on the fancier features Rust has.
This is only half true: every new release, the ABI of the runtime library changes. But the Visual C++ compiler ABI hasn't changed in decades.
If you design your own library ABIs so they don't pass std::foobars around, you can link C++ libraries built with VC++6 with ones built with the latest version, and the whole thing is going to work fine - the trick is that multiple versions of the runtime DLLs can coexist in the same process.
GCC/libstdc++ hasn't changed its ABI in more than a decade, since release 3.3 or 3.4; they even did incompatible changes to std::string in GCC 5 without breaking ABI:
https://developers.redhat.com/blog/2015/02/05/gcc5-and-the-c...
Or have their been issues with minor releases? I haven't come across any I can remember, but I might be wrong. Maybe some things were deprecated and then removed?
It’s an attitude that is difficult to convey because software developers always want to have the freedom to “improve” their own software, even at the possible cost of breaking something for some users (and worse, even telling users that they shouldn’t have been doing that in the first place).
I keep going back to this blog post which I wish more people agreed with (Steve Losh is another person influenced by Mercurial):
http://stevelosh.com/blog/2012/04/volatile-software/