I'll add one more point which the OP missed, but which is also very important. If you're developing an alternative implementation, you probably have a different architecture from the canonical version, and things that are easy to do in the main implementation might be very difficult to do in yours.
Let's say there's a piece of proprietary software for creating financial reports[1], which is using a weird binary format to store their documents. You want to make a free alternative, which can load and save these documents. The format is not fun to deal with, so you have a single function that loads the whole document into memory and one that dumps your data structures back to disk and entirely overwrites the file, but you operate purely on in-memory data while the software is running. What you don't know is that the proprietary software doesn't do this, it was developed in the days when users had very little RAM, so it only loads and saves the section the user is currently operating on, and knows how to modify the document in-place.
Then, the proprietary software introduces a way to add attachments to their documents. People keep adding stupidly large files, recordings of investor calls, scanned PDFs with hundreds of pages and so on. THe proprietary software loads documents one section at a time, so this works just fine. You, on the other hand, always deserialize the entire document at once, which suddenly becomes a problem when the documents are bigger than the users' available RAM memory. You now basically have to re-architect the entirety of your software, to support a change that took a week of a single developer's time for the main implementation.
[1] a purely hypothetical example, I've never actually worked in this space.
I work in an industry that requires 2 seperate implementations of all critical software components; so I have been involved in several reimplemenations to help satisfy that. There are definitely times where I saw a feature and thought "the original implementation was obviously doing X; and this feature does not really make sense given our architecture". However, I have never needed to rearchitect my program to implement the feature. The only real difference is that a trivial feature for one program takes a bit of work on the other. However, I could also point to functionality that took more work to implement on the original program, that the different implementation made trivial on the second program.
I have only had to do 1 major rearchitecturing, and that was on the original implementation, which had some architectural assumptions that forced an exponential blowup. I'm wasn't working on the second implementation, but they started years after us, and managed to avoid our mistake from the beginning.
To take a public example, that is far more complicated than anything I have worked on, consider the problem of running Windows applications under Linux.
Linux is a completly different implementation of a kernel than NT, and doesn't even attempt to be compatible. However, running Windows applications does not require a rearchitecture. Just a few relativly generic Kernel features, and a userspace compatiblity layer. Not to say that Wine does not take a lot of effort to write and maintain; but not nearly as much effort as implementing Windows itself. And, it is running on top of a platform that never aspired to be Windows compatible. Wine does, however, suffer from the problem that the article points outs, in that it is perpetually behind Windows. Wine also exemplifies a second problem, which is that bugs in the primary implementation end up being relied upon, so in order to be bug-for-bug compatible, you need to first discover what all the bugs you need to implement are.
Very interesting, what industry if you may discuss it a bit more?
My guess would be something safety-critical, probably aerospace. I haven't had to do the "2 separate implementations" thing yet but will likely be exploring quorom/voting-based redundancy pretty soon. Fun times!
I've seen this in aerospace in cases where testing the software against ground truth isn't possible for some reason[1], so you need to generate an alternative version of an algorithm as a sanity check.
[1] This is typically because the expected outputs are not easily known or specified in advance (e.g., interpolating a 3D wind cube to a higher resolution from lower resolution forecast data) and there isn't much if any experimental data, and collecting such data is expensive because it requires flying expensive aircraft around for long periods of time.
There are huge swaths of the Windows API that Wine straight up doesn't even try to implement, the complex APIs that a screen reader or driver would require to function being good examples.
There's a good argument that implementing kernel-mode driver APIs would be a complete waste of time, after all, Wine is running on top of another operating system, which should be responsible for interfacing with hardware. However, what the Wine developers didn't foresee was the fact that some applications (mainly games) started requiring a driver to function as an anti-cheat feature.
On the other end of the windows emulation space, another example is ReactOS, which has always lagged years behind its target.
Yes. This is the classic problem with making Python faster. CPython started as a naive interpreter, one that just plods along doing what the code says without optimization. In CPython, everything is a dictionary and there's little real concurrency. This allows any code to patch anything else on the fly. Nobody does that much, but people scream if you try to take that out. So implementations which really compile Python have to be able to handle some thread suddenly modifying something out from under another thread.
Classical Smalltalk feature `a become: b`, now everything is changed across the whole image, or after a redo in a Lisp Machine like environment.
Yet both ecosystems were able to come up with JIT implemenations that can handle the "reboot the world at any moment" use case.
Do you agree with them that "implementations which really compile Python have to be able to handle some thread suddenly modifying something out from under another thread"?
Would you say that's also true of Smalltalk implementations?
As if there aren't Smalltalk and Lisp implementations with support for parallelism, and have naturally to handle such cases.
The author mentions this when they talk about the ease of implementing new features in an interpreted language vs. a compiled language.
Hm, this is true, but I think it's worth pointing out that this is more general. The article was focusing on how JIT is harder than interpreting, whereas neither model in parent's post is inherently more complicated.
Sometimes this difference in architecture is deliberate, too. For example, the GNU versions of a lot of basic Unix utilities make completely different tradeoffs from the original Unix (and BSD) versions specifically to resist allegations of copyright violations.
With the Innovator’s Dilemma the reference implementation could be a competitor’s. I’ve seen this play out well and badly. Having better knowledge of the market now, you might pick a better architecture, and find yourself in a place where it’s cheaper for you to add new features quickly than for your competitor. That’s one of the ways a smaller company can raise your overhead. Or one who has ignored less tech debt.
In fact this is one of the few times you can clearly illustrate tech debt to management. It just takes us longer than Acme to implement these features.