(update-vals customers #(update-in % [:address 0] str/capitalize))In this sense it's closer to Clojure's transients, if Clojure came prepackaged with special mutation syntax (notably assignment via =) and if transient-ness could be propagated transitively to all nested structures in a data structure.
(update-fn (fn [{:keys [address]}
:as customer]
(if address (update customer :address clojure.string/capitalize) customer))
customers)
which pattern matches on some `object` (map) and does processing. We find it less fragile than specifying explicit path to an element. It can also work in a polymorphic fashion. On the other hand, there is a risk of a false positive (when you modify address that you shouldn't). But you can mitigate that risk by using additional checks (in case of a customer, you can check for additional set of fields that are specific for that object(map)
edit:formattingMost good FP data structures will be tree like, but with a low branching factor and chunky leafs. Now of course that does not save you from lots of memory consumption if you use a language/platform where even primitives are boxed, like the JVM. But that is a completely separate topic.
To be clear it is certainly a solvable problem (as this post shows) but it can make things difficult.
It's very costly though.
Under the hood it's a effectively a single CAS instruction that loops on failure (which only occurs under contention, but then you have waiting with locks too).
The point is neither copying the whole structure nor diffs are as easy as mutex + inner mutation.
Although I don't know what you mean by growing lenses.
Mutex + inner mutation is no easier than CAS (which is the usual solution with concurrent writing of immutable data structures) a la Java AtomicReference or STM (another popular one) and in my opinion significantly harder as soon as you have multiple mutexes.
I am not against FP I think the point of "it is good if you can fix performance" is very accurate. I just think it is also important to acknowledge that the paradigm can be more complex in situations where it is supposed to help leading to a mixed bag.
Similar to distributed databases. Your database can now be phenomenally powerful but you can't do a stored proc anymore without losing that power.
It can be a very effective and totally worth it but it isn't a pure win necessarily.
Why is that?
CAS is a primitive, it isn't complex itself but it can be complex to work with once you are talking non trivial work. Just like locks. A global lock is dumb simple but a real locking system can be as complex as you will let it.
Right but both of those things are true for locks too right? CAS seems no harder than locks.
Additionally while multiple locks is annoying it is way easier than multiple CAS. "Have a global order for locks" is the hard but solvable problem for multiple locks. For CAS if you need to CAS two dependent things you... I don't know it depends.
It's hidden in both cases (usually CAS instructions are hidden behind an `update` interface as in Java's Atomic* family rather than directly used in the same way that generally a lock is an interface for a TAS instruction/spin lock + upgrading to wait queue).
> For CAS if you need to CAS two dependent things you... I don't know it depends.
You use nested atomic references. Just like locks it's not a great way of doing things, and an analogous problem to ordering locks rears its head (by virtue of nesting you cannot mess up ordering in the strict sense, but you can accidentally "cross the boundaries" of two atomics in an update that goes against the nesting order), but it's doable in the same way as locks.
The usual CAS-like but better approach is STM (which is where immutability really shines).
I'm still not seeing how CAS is any harder than locks.