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.
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.
Yes, because our program probably needs more than one Client.
But my Sword class doesn't hold a reference to a NetState object.
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.
const read = (fileHandle) => {
return () => {
if io:isOpen(fileHandle) && io:hasNext(fileHandle) => {
return io:readNext(fileHandle)
} else if io:isOpen(fileHandle) {return undefined
} else {throw "File is closed"}
}
}(myFile)
read()
read()
io:close(myFile)
read() //throwsShow 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.
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.
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.
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.
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.
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.
For sufficiently complex systems I don't see how functional or other paradigms can manage without holding a huge amount of global state.