Hacker News new | ask | show | jobs
by tcgv 1598 days ago
I always bring up this paper when I read a post criticizing OOP. It's a bit old but still very relevant and practical. In it the concept of information hiding, closely related to encapsulation, was first described. This concept plays a central role in the strategy for effective system modularization and is IMHO the basis of OOP.

Shameless plug: A couple of years ago I wrote a post about this paper trying to expand it based on my personal experience and providing a more simpler example to elucidate the concepts presented in it [1]

[1] https://thomasvilhena.com/2020/03/a-strategy-for-effective-s...

3 comments

I don't remember seeing a criticism of OOP that is a criticism of encapsulation. Everybody tends to agree that encapsulation is desirable. It seems to me criticisms of OOP are that it rolls too many things into one construct, the class: it's a type, with encapsulation of methods and data structure, plus inheritance, all into one. And it can lead to not so efficient data placement on modern CPU (AoS vs SoA).

Languages like Ada and Ocaml have object orientation extensions to the initial languages (Ada 83, and Caml/SML) that can very often be ignored. And still are very good for encapsulation. With them, modules (packages for Ada) and types are separate. It is very natural to group close types together into one module, while exporting opaque abstract types only usable through a module services.

Rolling different concepts into the class may give a more intuitive result at first, particularly when simulating real world entities. But it's also a bit limiting compared to keeping those concepts orthogonal.

I think Clojure quite explicitly rejects "encapsulationism". As does the "data on the outside" philosophy of event driven systems.
Many times one wants to encapsulate is to protect. Data is immutable in Clojure, which goes away with problems arising from everything accessible in wild west.
This is notably something haskell has largely been worse at than other languages in the ML family (only recently adding the backpack module system, which appears to be much less capable than the module systems in extended versions of SML and OCaml and is still not supported by tools like stack if I understand it right).

Haskell has modules, but until recently it had no module interfaces, so you could not write your code to depend on an abstract type and associated function definitions that could be swapped out dynamically (for ex: w/ mocks in testing).

Type classes are a related concept (i.e. an abstract definition of functions that may be implemented for a given type), but they enforce additional restrictions like coherency (i.e. only a single instance of class may be implemented for a given type). While this is advantageous in many situations, it's a huge pain in the ass when you need to do something like just mock network IO somewhere (as every combination of things you want to change out is going to need a newtype wrapper + class instances for all effects).

The preferred techniques for handling this the last time I used it were:

1. Transformer classes (usually w/ the "mtl" library + your own custom ones). Basically you could categorize types of useful effects into classes, use those class constraints in your function definitions + push any concrete implementations as high up the stack as possible, and use different monad transformer stacks to swap out the implementations of those effects. There's no getting around the single instance restriction of classes, but this allows you to only change out one "layer" of effects in code (say you have a transformer for network IO, you could change out only that concrete type), which reduces some of the labor involved. But there are subtle implications about the way transformers stack that change the meaning of your code, and the use of functional dependencies in MTL means you can only really use one instance of a class in your stack, so for using MTL to do something like things supply your functions w/ context/config values via MonadReader, you end up needing to smuggle around some unholy god object of everything you'd ever want to inject. Enjoy trying to write legible test cases w/ that.

2. Roll your own classes. This bypasses a lot of the weirdness you run into w/ transformer stacks, but its frankly a pain having to break out every possible effect you'll make use of into classes + defining instances for different use cases. And then you'll run into the reality that many libraries are making use of MTL-style classes so you can't entirely avoid them anyways (though you usually won't need to use MTL class constraints in your own code, and thus sidestep the aforementioned god objects).

3. Free monads. Basically describe your program as functions that don't actually run effects, but instead produce data structures describing a program that can be interpreted in several ways where the effects actually occur. This sounds awful but it's very easy to reason about as you have full control over the evaluation of effects in your interpreter functions, has a lot of nice advantages for testing + debugging as everything that could possibly cause a side effect is now inspectable as data, and is usually the least laborious of all solutions in my experience? This is known to be suboptimal from a performance perspective though.

All that said, backpack seems like it will be an improvement upon all these options, despite not having the full flexibility you get from first-class modules in other languages in the ML family tree.

One other note: 1ML looks like a very cool as a refinement of SML modules [1]. I have no real expertise w/ the design of programming languages to know if there are other problems with this approach, but I'd love to see a production-ready implementation.

[1]: https://people.mpi-sws.org/~rossberg/1ml/1ml-extended.pdf

The problem with OOP is that the code using it has tendency to produce way too many objects thus putting into the code rather rigid assumptions how the system may evolve. This essentially contradicts what the paper advocates which is more like a toolbox approach.
Loved this post