4 ms·
This both is and isn't true, and the finer points revolve around mutability/concurrency and implementation inheritance. Modern computers are inherently concurr
by ledauphin 6y ago
This both is and isn't true, and the finer points revolve around mutability/concurrency and implementation inheritance.
Modern computers are inherently concurrent and mutable, and these fundamental realities have been shown over and over again to cause hard-to-understand issues in sufficiently complex codebases. In the vast majority of OOP languages, instantiated classes are mutable-by-default and can be mutated by multiple concurrent operators (threads, coroutines, etc). This makes them actually quite similar to the underlying computing model - even though they're a high-level abstraction, they force you to deal with the knowledge that the memory at any address can be modified at any time with no warning. [1]
Classes in most languages also offer implementation inheritance, which boils down to an approach to "rebundling" the constituent functions in ways that are arguably simple enough, but generally in practice it has been found to be hard for programmers to reason about. Dynamic dispatch is very powerful, and the way that most inheritance mechanisms expose this seems to be a model that programmers understand in theory but fail to consistently remember in practice.
In my opinion, the fundamental difficulty with classes is that in most languages, you get all of these tools bundled (complected) into the same toolbox without any say in the matter. If you didn't want inheritance, too bad. If you didn't want concurrent mutability, you're now being asked to design a complex state machine to prevent it, rather than it being excluded off the bat by most functional approaches. Functional approaches force you to opt in to most of the footguns that are included "out of the box" in OOP.
Classes will remain the best approach in many OOP languages for RAII-shaped problems, where some part of an application needs to encapsulate and manage a limited, shared, and potentially mutable resource (such as a file handle, a database connection, or an in-memory cache), because classes are an abstraction that maps cleanly to shared mutable state with locking mechanisms.
[1] You can of course also shoot yourself in the foot with closures that themselves contain mutable state (e.g. some kind of mutable container), and I think this is a rarely-considered danger for functional paradigms in languages that default to mutable data structures.
- pjmlp 6y agoThis is one of the C++ features that I loved since the early days, the ability to control how immutable a class actually is.
- SolarNet 6y agoI'm not sure you are really disagreeing. Which is to say I don't see where the not "isn't true" part come from. Yes classes are closer to the physical reality of modern computer (and that is one reason I prefer C++), but they are still the same construct as a closure and vice-versa. Everything is just implementation details. Again I grant that languages designed around classes (or really in my opinion, around the physical reality of the device implementing them) are more useful in practice. But from a conceptional abstraction point the post is true.
- chowells 6y agoIt's not true in the sense that classes are higher order functions + a lot of junk that the GP examined in some detail. All that junk adds a lot of complexity and makes classes significantly harder to work with.
- stopachka 6y agoWell said ledauphin!
- zozbot234 6y agoImplementation inheritance is not "simple" at all. It's not just "rebundling" class methods, it allows the 'rebundling' to happen at some arbitrary place in the class hierarchy in a way that inherently involves dynamic dispatch/late binding for every method (virtual) call. (Unless the set of callers and derived classes is entirely known in advance and can be dealt with via devirtualization, of course.) That's a very loose semantics, and it's no wonder that developers find it "hard" to reason about.