Hacker News new | ask | show | jobs
by na85 1967 days ago
I disagree. Good OO is about encapsulation of concerns. Make a Client object and push data to it; let the class worry about the networking and sockets. Make another object to deal with the database.

For sufficiently complex systems I don't see how functional or other paradigms can manage without holding a huge amount of global state.

4 comments

The function system doesn't hold 'global' state. It holds the state in arguments given to functions. If you want a client, you still have a bit of client 'data' and you pass that around your functions.

If you wanna do stuff to the client, you call functions with the client as an argument. If you want polymorphic clients, you define a client typeclass / trait / interface called Client, and then your functions take a generic Client bit of data.

In some sense, this approach is a lot like replacing foo.bar() with foo(bar). Which doesn't do much to change your program. The interesting difference in FP is how you 'change the state' of your client.

In FP, if you want your client to e.g. count its connections you would do something like `nextClient = connect(oldClient)` instead of the OOP `client.connect()`. This means that you are a lot more explicit about your state changes. This has a lot of advantages that can be hard to wrap your head around. It also comes with some disadvantages.

As a result though, all of your state is carried very 'locally' in your functions scopes.

How do you manage to not have global objects?

FP and imperative handle data without huge amounts of global state the same way. By structuring your code properly.

Per your example, your Client instance still has to be passed around everywhere it's needed, and you call client.push_data() on it; in FP or imperative approaches, you would pass around a Client struct or tuple or similar, and call push_data(client).

OO just bundles state and functions together, as fields and methods on an object. Which has its pros, and its cons.

> Per your example, your Client instance still has to be passed around everywhere it's needed

Yes, because our program probably needs more than one Client.

Not sure if you're trying to make a point here?
I mean in Java or C# there are "global" objects, being the Main class, I suppose. There's always a top-level construct.

But my Sword class doesn't hold a reference to a NetState object.

That was a rhetorical statement. The same reason you don't need global objects in an OO language is the reason you don't need global data structures in an imperative or functional one.

Your Sword class doesn't hold a reference to a NetState object, but then, my Sword struct doesn't need a reference to a NetState struct, either.

If I were to climb the reference hierarchies back to my 'main' function, I would find a common ancestor, just like in OO if I were to climb my reference hierarchies, I would get back to the class with my 'main' method. But that doesn't imply global state; it's all scoped down in the code where I defined and use it.

I haven't the faintest clue what point you're trying to make but I'm sure it's very clever.
The point is the graph of data dependencies in an FP system doesn't necessarily look any different than in an OO one.
Closures and internal state of objects are basically the same thing.
Separating concerns the way you describe can be accomplished leveraging the polymorphism mechanisms in an OO language (objects and subclasses) or just as easily in a functional style (multimethods, protocols, interfaces).

Show me an example of something you think is decoupled in OO style which could not be similarly decoupled in FP, and I'll give you the FP version which does the same.

Not saying one is better than the other, but you can definitely keep your code decoupled in both styles.

In an OO program all state is global, because you can't understand any given object's behaviour without knowing what its state is and what the behaviours of all the objects it communicates with are, and you can't understand those without knowing their states either.

State can and should be hierarchical - and you can do that easily in FP - but the idea that OO lets you make it somehow non-global is a myth.

>In an OO program all state is global, because you can't understand any given object's behaviour without knowing what its state is and what the behaviours of all the objects it communicates with are, and you can't understand those without knowing their states either.

By that logic all state in any program is global.

But if I'm writing an MMO server my GoldCoin class doesn't need to know about the client's connection state.

Have you actually written or worked with OO code before? Your comment reads like you have not.

> By that logic all state in any program is global.

All state in any program is global in the sense of being reachable from top-level. You can, and should, make your state hierarchical - reachable from top-level should not mean reachable from anywhere, you can have (non-toplevel) parts of your program that only access parts of your state. Unfortunately OO is uniquely bad at this, because objects are encouraged to have hidden coupling.

> But if I'm writing an MMO server my GoldCoin class doesn't need to know about the client's connection state.

But you have no way of knowing or enforcing that. "You wanted a banana but what you got was a gorilla holding the banana and the entire jungle" - your GoldCoin might contain a ("encapsulated" i.e. hidden) reference to another class that has a reference to another class and so on, and so eventually it does depend on the client's connection state.

> Have you actually written or worked with OO code before?

Yes, for many years, which is why I've become an advocate of FP style instead.

> All state in any program is global in the sense of being reachable from top-level.

Well, think about a random number generator. It has some internal state, which gets set by some action taken (perhaps indirectly) by the top level. And that state is "reachable" by getting the next random number, but that random number may not be a direct representation of any part of the generator's state. Also, after initialization, the generator's state should not be alterable by the rest of the program.

So to me, that's not really "reachable". The entire point of encapulating that state in the random number generator is to make it not reachable.

That's a perfect example; in my experience that kind of RNG state is global (or at least, it has all the problems of traditional global state). Potentially any object might behave in a strange way, because it contains a reference to that RNG (hidden from you): you might find a sequence of calls that usually, but not always, reproduces a bug in test, and then it turns out that it depends on whether other tests running in parallel are affecting the RNG state. Essentially any test you write that's bigger than a single-class mock test has to be aware of the RNG and stub it out, even if you're testing something that on the face of it has nothing to do with RNG.

In a functional program you would make the RNG state an explicit value, and pass it where it's used (but not where it isn't - so there might be large program regions that don't have access to it). It'll be an explicit part of the top-level state of your program. I'd argue that that's not actually any more global - or at least, it causes fewer problems - than the OO approach.