Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The problem with OOP is not just with poor implementations and inheritance models (e.g. Java) but that state encapsulation is actually bad and cements complexity.

The real goal should be to untangle state and decouple it from computations by putting it all in global shared state (e.g. SQL, Redux, ECS frameworks, etc.) while using stateless functional computations to compute changes to the global state. Pure functions are composable in a way that objects simply aren’t.



The opposite end of OOP is the functional purist world where "color = blue when hovering over button" becomes 10 levels of boilerplate indirection in order to update global state and receive changes using actions/reducers/containers/memoizers. It's just substituting one type of needless, brittle complexity for another.


Exactly and the functional way is harder for humans to grok


That’s fair and as someone who has that same frustration with it I realized Redux might not be the best example precisely for this reason. I’d argue though that the problem here is the premature abstraction of encapsulating state in actions/reducers/memoizers. This sort of thing would be way more natural in an ECS framework.


Functional purist here (mostly Elm), and I have very few problems that arise from the paradigm. Also fewer bugs than pretty much any software I use or deal with.


How is state encapsulation bad? Encapsulated state means that only a very few functions can access/change that state. Non-encapsulated state means that any code in the entire executable can change that state. How is that better?

> The real goal should be to untangle state and decouple it from computations by putting it all in global shared state (e.g. SQL, Redux, data oriented game frameworks, etc.) while using stateless functional computations to compute changes to the global state.

I kind of presume that you're in a single-threaded world. Shared mutable state plus multithreading is a recipe for disaster.


In functional languages you can still ensure that only certain functions can change certain parts of the state. The big idea is that state is isolated. You can actually write functional code that looks a lot like OO code (essentially just calling `foo(thing)` instead of `thing.foo()`) but because state is isolated, you know that nothing is changing behind the scenes. It may not seem much, but it's actually really freeing to not have to think about that stuff anymore. Same thing with immutability.

I was skeptical of functional for years and was actually a big OO zealot. I finally brought myself to give functional a serious go over 1.5 years ago. I now write it professionally and don't miss OO even a tiny bit. It's really just something you have to try and have an open mind about. I actually still find myself sometimes thinking, "Crap, maybe I should make a copy of this in case something else tries to touch... OH WAIT! IMMUTABILITY! I'M SAFE!" It's a really nice feeling :)


Let me separate out immutability for a moment.

If I don't have immutability, then if I can call foo(thing), then I can write bar(), and then call bar(thing). Bar can now alter thing. So my encapsulation can be broken by someone just writing a function. (This is the same problem that C had with structs - anyone could write a function to alter the data in your struct, and thereby put it in an inconsistent state.)

Now, with something like C++, you can still do that. You have to go to thing, though, and write a new thing.bar(). It therefore becomes much clearer that bar() may break the consistency guarantees of thing.

So I don't think that just functional gives you the guarantees that OO encapsulation does.

Now, immutability changes things... a little bit. But with immutability, the problem only moves, it doesn't go away. Someone can now write a bar() that returns a new/altered thing, and then pass it to me. I can still get a thing that is in a state that violates the rules for what a thing is supposed to be.

And, once again, the same thing can happen with OO. It's just that, if it happens, you have a lot less code to look through to try to figure out how and where it happened.

You may have noticed that I care a lot about data being in a consistent, valid state. If you don't care about that, then my arguments may not resonate with you.


You can’t separate out immutability because without immutability it’s not functional code, it’s just procedural code.

To the question of consistency though, OOP gets you very little because consistency rarely maps directly to objects. So if you end in a situation where object A is inconsistent with the object B, you still have to trace out all the locations where object A and object B might have been mutated and figure out what combination of code causes the issue.

At least in the global state system you can get runtime consistency by running consistency checks on each attempted transaction.


There are two types of "data" in a properly encapsulated class [1]:

1. Internal/private data -- things like a pointer to a string's contents, its length, and the capacity of the content buffer.

2. Public "data" (API) -- best provided as accessor methods/properties (and public methods) to ensure that the class' invariants hold if these can be used to mutate state. You don't care if the object has been mutated through this public API as this is the contract for how the class/object instances should be used.

A properly encapsulated string class is free to change its internal state/data (e.g. start+length+capacity, or start+end+endOfBuffer), as long as it keeps the contract defined by the public API intact. The same applies for data structures, mathematical objects like complex and rational numbers.

You could store a complex number in polar (r, theta) or cartesian (x, y) form and provide public accessors for all of those values. If you had setters for those, the native representation would be a simple assignment, while the other representation would do the necessary polar <=> cartesian conversion. This would maintain the invariant that the two representations are equivalent, such that if you set r then the angle (theta) does not change, but the magnitude changes such that it is equal to r.

Note that if the complex number (or string) was modelled in a procedural or functional language, you would have to choose and stick to one or the other representation. That structure or type definition is leaking the state, such that a program could modify the length of the string without updating its contents.

Q: Can you give an example of where having object A inconsistent with object B is an issue? That would help me to understand the issue/problem you are referencing.

[1] Many OOP languages also allow protected data, which can be seen by implementations but not any other code. These are risky, as they can allow the derived class to break any invariants on that protected data like you described in your second paragraph. As such, I try to avoid them wherever possible in my own code, but will run into them in thirdparty or system classes.


Your last sentence there is pretty unnecessary. Moving along...

Echoing my sibling comment: immutability is a defining feature of FP, so there is no way to put it aside if we're talking about FP.

As for the rest of your argument, I'm a bit of meathead and maybe haven't followed it fully. My thought is that in OO, an instance of an object can be passed some data that it processes along with its own internal state. If that data is a reference to another object, then that object could be changed while the receiving object is doing its thing. The receiver then would go and store its results within itself.

In functional, functions receive everything as parameters and all that data is guaranteed not to change for duration that the function runs. Once it returns, it will update the global state and then, again as my sibling comment points out, it's far easier to check validity of the whole shebang as it's in one place.

As for "you have a lot less code to look through", I feel this is a common thing said by folks who have not really grokked how FP works. I say this because I used to make this argument ;P While FP can be slightly more verbose than OO, I've found FP way easier to figure out where things came from.

All in all, I don't have a vendetta against OO. I was really just trying to get across that I was a big OO enthusiast and went in FP perhaps even with a closed mind and it didn't take much to win me over. Having said that, I'm in the Elixir/Erlang world, which is a bit of its own beast.

(Edit: your/you’re/yore)


I also want to say that I'm in no claim that FP is a silver bullet. All-in-all, programming is terrible, lol (naw, I love it). I've just found myself carrying around fewer footguns since I converted and generally less stressed and confused by what's going on... even when reading less-than-ideal legacy code.


> Now, with something like C++, you can still do that. You have to go to thing, though, and write a new thing.bar(). It therefore becomes much clearer that bar() may break the consistency guarantees of thing.

Well, no it doesn't. I've worked with too many codebases that has a declaration of `void foo (sometype_t &instance);`. Then when you read the code and see a function call of the form `someFunc(localInstance);` you have no idea if someFunc can change localInstance.


Encapsulated state is bad when it encapsulates the wrong state. The wrongness becomes less visible, but appears more correct, because by mere assertion the encapsulation can boldly define the correctness of something wrong. In novice hands that's very dangerous and long-lasting and is the unsafe pointer equivalent of architectural design.


Are you saying we should remove local state? If you aren't then why is that better organized than object state?


Few people are organized and use encapsulation to store things where they are easily retrieved. Most people are messy and encapsulate to hide junk under the couch.

It's the code monkeys, stupid. Given a powerful enough machinery most code will look like junk. That's why some "beautiful" languages today tie your hands and dumb everything down so you aren't free to do as you like, whereas assembly was just fine 40 years ago.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: