Well put. To turn it around, the ideal of state management is:
- Immutable, isolated state
- State operations are singular - I guess meaning centralized as well as atomic
- State operations are modular and cohesive units of code
- Can be understood in its totality - which implies centralized state management, where operations are not scattered thoroughout and intertwined with, for example, UI code
In my experience, I generally agree with the sentiment against "proper" OOP, as an accepted or preferred style of code organization. This is related to some fundamental truths and advantages that the rising popularity of functional programming is revealing.
Even in the world of UI and software for the web, the classic OOP patterns are getting "disrupted" in a healthy way, and it seems to be moving in the direction of composing immutable states, idempotent functions with no side-effects, where state changes and effects are centally managed in an isolated way.
State and zip code always change together. Having had to fix code that passed 5 variables around for an address (which needed to go to 6 for “address 2”) I replaced it with an address object.
For me, true OOP requires some sort of inheritance and/or runtime method dispatch, at least.
Can we at least agree that limiting operations on the structure to the code that controls that structure is following the definition of encapsulation, which is what I should have said anyway?
Not sure I understand what this means. Could you give an example?
But then again, what you want there is modularity, which OOP provides, but is also well-supported in other paradigms.
Here's a data structure that represents a bill of materials. It has a list of components, and a total cost, which is the sum of the costs of the components on the list. That's the invariant - that the total cost is the sum of the costs of the components in the list.
If it's just a structure, then someone can add or remove a component, and forget to update the total cost, and the invariant is violated. But if it's an object, if the code is bound to the data (in the way OOP normally calls encapsulation), then only the member functions can modify the structure. So if you want to add a component, you have to use the object's addComponent method, which (in a properly debugged object) isn't going to forget to update the total cost.
Now, you could say that the structure could have a code library to go with it, and that could have an addComponent function. That's true. But the user doesn't have to use that function - they can mess with the list directly, if they think they know what they're doing. Whereas with OOP encapsulation, they have to use the existing function. That function can guarantee that the object's invariants are maintained.
There's a potential performance cost here, but that usually doesn't matter a whole lot.
Yes, but this is not possible in general. For instance, what if you want to endow your List with a "MaxTotalCost", set at creation. You can't ensure that your list respects that invariant, other than by encapsulating it behind some custom interface.
[1]: https://craftinginterpreters.com/representing-code.html#the-...
All this said, this is basically OOP in C, and the code is still bound to the data, only it is bound by naming (point_add()) not by a scope (point.add()).
You can achieve the same kind of hiding in Java by using an existential type. E.g. you could have a library that looked like:
interface Cursor<T> {
T start();
T nextStep(T currentState, int move);
void finish(T);
}
class DataStructure {
Cursor<?> traverse();
}
And then you don't know what the internal state of the cursor is because the T type is hidden from you, but the compiler checks that you always call nextStep() with the same state you got back from start(), and you can't possibly mix up the state from two different cursors.I love opaque structures, but the one downside in embedded is they can’t be truly opaque since you want to avoid dynamic memory allocation - and therefore the place you instantiate the struct needs to know the size, and therefore members of the struct.