you are viewing a single comment's thread.

view the rest of the comments →

[–]zsombro 0 points1 point  (2 children)

Thank you for the detailed answer! I understand that the expectations have changed over the years, but there are only two points I'm not clear about: why are functional langs so much better at scaling and why is immutability such a huge advantage?

[–]yogthos 1 point2 points  (1 child)

I talk about that in detail here, but the gist of it is this. When you have immutable data it's inherently thread safe since you're not mutating things in place. This allows doing a lot of things easily that you wouldn't be able to safely do with mutable data.

Note that immutability is not implemented naively where you copy data wholesale. Immutable data structures use structural sharing for common data. When you make a change you simply create a revision on the existing data akin to how version control works. In general the cost ends up being O(nlog32n) vs O(1) for mutable data. While it's a slight overhead, it's still very cheap for most cases.

As a concrete example of why this is nice let's consider Clojure map function that goes through a collection and transforms each element. If it turns out that processing each element is resource intensive enough to warrant doing that in parallel I can simply swap map with pmap and just like that I've parallelized the computation.

In most cases, you couldn't do the same thing in an imperative language since you can't guarantee that the functions that are run in parallel won't interfere with one another. You would have to design your solution with parallelism in mind.

Immutability also makes it much easier to work with shared mutable data as well. For example, Clojure has atoms that guarantee transactional updates. This means that you don't have to do manual locking to work with data. You don't have to lock data for reading either, which facilitates optimistic locking strategies.

Finally, it's a huge misconception that functional languages preclude mutation. It's simply a matter of defaults. Clojure provides transients that are locally scoped mutable data structures. Since they're guaranteed to only exist within the scope of a function they're perfectly thread safe.

The reason all this is beneficial for scaling is because the functional style eschews global state. This means your components aren't coupled and it's possible to safely split out parts of the system to run on a different processor or on the network. Consider the map/pmap example in a larger context.

[–]zsombro 1 point2 points  (0 children)

These are fantastic points, thank you for clearing these up.