Hacker News new | ask | show | jobs
by applepple 1862 days ago
Functional programming is OK up to a certain degree of complexity... Good if you have a lot of junior developers on your team because it prevents certain kinds of mistakes which juniors tend to make. But many FP projects end up becoming a giant pile of spaghetti code eventually.

I think this is because FP doesn't put emphasis on separation of concerns like OOP does. FP advocates separating state from logic as being more important than separating concerns across components.

With OOP, if some piece of state concerns the same business domain as some piece of logic, then they should be co-located. On the other hand, FP will happily violate this principle in favour of keeping state and logic separate.

Having built highly complex, modular systems with OOP, I don't see how it would have been possible to implement these systems in such a modular way using FP... Co-locating related state and logic is absolutely essential to achieve modularity.

I have yet to see any complex FP system which was not spaghetti code.

2 comments

Obvious troll is obvious but... I assure you there are many high complexity systems in production today using FP that are not spaghetti code. Just like there are OOP codebases which are spaghetti, abstractions are used or abused. I’ve seen both very bad and very good Ruby projects. OOP wasn’t the direct reason why they were one or the other.
OOP also leads to spaghetti code. Everything turns into a steaming pile of crap if you don’t work to avoid that reality.
Yes, but the difference is that OOP can scale indefinitely but FP cannot.

If you don't co-locate related state and logic, ensuring that each piece of state reaches the correct components becomes a logistical nightmare at scale (either needs to traverse many intermediate components or needs to be exposed globally to all components).

If you don't allow components to mutate state locally, you need a strategy to distribute all your data directly from external stores to the correct components and also a strategy to send state updates back to the relevant stores. When you have multiple components which share some of the same data, this is impossible to do in an efficient and reliable way because of the latency between the external store and the components (components risk overwriting each other's data by sending conflicting updates, for example).

It's a logistical nightmare, because, without co-locating state with logic in some components, you cannot control how state updates are combined and then applied onto the external store. Components have no awareness of each other so they cannot coordinate. Every read and write has to go through the external store which is both inefficient and unreliable when you factor in latency. (The idea of caching violates FP principles).

The advantage of co-located state is that it can be updated synchronously without any latency so there is no possibility of conflicts.

>> If you don't allow components to mutate state locally,

Code mutation is, in my personal experience, a shit show. With Elixir, you I have to worry about some random process mutating your data because it can't, as it's literally immutable. I have never, not a single time, wished I could mutate a data structure in Elixir, because I can think of no case where it makes my life easier. Even quasi-objects, in the C++/Java OOP sense, like GenServers w/ internal state may appear to be mutating data from the outside, but from the inside they still rely on copying the data to update the state. It's so much easier for me to reason about.

Maybe different things simply appeal to different people. I could make arguments for OOP being harder to scale, but maybe that's just true for how my brain works.

State mutations are safe and easy to handle if the state is fully contained inside a blackbox (a class) and you only return copies of the state but never the actual references.

A blackbox should never expose object references (its internal state) to its parent components. It should also avoid passing object references to child components unless it's sure that the child component will never mutate this state in an unexpected way.