Something I find kind of interesting is that I am old enough to have lived through, or seen the direct aftermath of, a few different iterations of software development culture. I was reminded of this when sighing at an OctoberProject stack trace pointing out something which really should be a static type check: Mutability.
This is due to the different priorities in the mid-late 90s, when the Java collections classes were being written, and more recently. Back in the 90s, OO was all the rage and designing over-complicated relationships between different ideas was popular. Don't get me wrong, the collections classes actually have a pretty good relationship which is an example of that era's culture done right. Plus, Java has interfaces which protect you from the most awful problems one can see in that era (IOStream, anyone?). So, while not terrible, it is definitely designed within that culture whereas I am looking at this through a more recent lens.
Fast-forward to today were there are very different priorities. Languages like Rust are a perfect example of a few points I find preferable, today (just ignore implicit destructor calls and not-always-allowed type declarations). First of all, we are no longer drinking the OO Kool-aid since it turns out that really just allows for hidden complexity, over-design, and bizarre in-memory data shapes (IOStream, anyone?). However, things like traits (aka protocols for the Objective-C people, or interfaces in Java parlance) actually make sense since they allow you to declare what something needs to do, not what it is (there is a Batman joke in there, somewhere), while simultaneously making the implementation inheritance anti-pattern not easily done. However, something I like most is default immutability. In Rust, everything is immutable unless explicitly declared otherwise. This is such a good idea that I would include it in my own C derivative (if I ever get around to needing to build that).
So, that brings us back to today where OctoberProject makes heavy use of immutable wrappers or explicitly immutable types.... which the type system happily treats as mutable, thus leading to runtime errors when some unexpected code path passes a read-only snapshot into a mutable path. Now, although it might seem like over-kill, I think a good solution to this may be to use my own wrapper classes/interfaces for the collection types so that I can convert this into a type system problem and solve it statically. After all, just because they didn't have the foresight to know the technical trends 25+ years into the future doesn't mean that they didn't see the genius in an explicit type system.
Ideally, this would just be 2 interfaces and 1 concrete implementation for each relevant type (probably 2: a list and a map), but I do get ahead of myself in wondering about interface versus virtual dispatch performance. Hmm, it is probably fine. Of course, I might need a second concrete implementation, anyway, to distinguish between physically and logically immutable. That is probably more correct, to avoid inconsistent aliasing of snapshot data.
Hmm, not sure,
Jeff.