4 ms·
I don’t actually get the big deal about enforced immutability. Of course, you need to constrain yourself in certain circumstances to do things immutably, but it
by abrax3141 7y ago
I don’t actually get the big deal about enforced immutability. Of course, you need to constrain yourself in certain circumstances to do things immutably, but it seems like something that should be contextual (immutably ...), rather than enforcing it. In fact, sometimes it is necessary in high performance multi—threaded/multi-processor environments to use side effects judiciously. Moreover, most serious lisp programmers almost write immutably by default. The only (common) exception being hash tables, but you just don’t use them if you need more control.
- _bxg1 7y ago> sometimes it is necessary in high performance multi—threaded/multi-processor environments to use side effects judiciously The short answer is: Clojure (and other languages/frameworks that emphasize immutability) is not intended for those use-cases. The long answer is, Clojure added something called "transient data structures" as a trap-door for cases where you really absolutely need mutability for performance: https://clojure.org/reference/transients https://clojure.org/reference/transients But they are really intended to be the exception, not the rule. Enforced immutability is a conscious choice you make to trade some performance for increased robustness. Also, compared to "immutability by convention", enforced immutability allows certain optimizations to be made that greatly decrease the wasted memory and allocation/deallocation that would occur if you naively cloned everything.
- abrax3141 7y agoI haven’t really thought this through, but at the OS level we used to have the concept of reentrancy, which separated code from variables for multi-tasking. I haven’t seen this exact concept in a programming language level, although I don’t know every language, and it may exist by another name. I didn’t really think about this until now, but I naturally write reentrantly, separating the hash tables and globals For cross thread data, and writing everything else functionally, and/or using the thread system to manage all other mutables as locals (like the transient construct, I guess).
- _bxg1 7y agoI think the idiom in Clojure is for threads to be functional, so they aren't running persistently and mutating shared state: they spin off to do nonblocking work and then return their result. While doing so they share memory so as to avoid serialization/cloning overhead, but that shared memory is an immutable data structure, so you don't really have to worry about race conditions because threads can't step on each other's toes.
- didibus 7y ago> but it seems like something that should be contextual That's exactly what Clojure does. It makes immutability the default, but offer tons of mutable options with varying degree of contextual restraints for performance. For example, by default your data-structures and record types are immutable. Now, say you can tolerate some mutability within a local context, you can make your immutable data-structures temporarily mutable (called transients), apply mutation for performance in the local context, and make it immutable again once done. Another example is, by default, Global Variables are immutable and can't have their value changed, but, you can have them have per-thread local values, which are then allowed to be modified, since they're concurrently safe. Another example is local variables are also immutable by default, and so are data-structures, but if you need to mutate the variable, you can make it `atomic` or `volatile` depending on the level of guarantees you need around concurrent access. Now you'll be able to change their values, for the former, using an atomic eventually consistent swap and set algorithm, and for the latter, with CPU cache synchronization. There's more, but basically, the idea is to start with the safest default, and for every new level of: "I need a bit more performance or flexibility", there is an additional construct that can be used to enable it. All the way down to full blown mutable variables and data-structures when absolute performance is required.
- int_19h 7y agoThe point is to force you to think whether mutability is necessary or not in every given case - you have to, because you have to consciously pick it.
- armitron 7y agoMost serious Lisp programmers write mutable code. Common Lisp is multi-paradigm but most code written has side-effects. And that’s for a good reason: Clojure-style immutable programming doesn’t fit most problems very well. Which is why Clojure will never be a language with the lasting power of Common Lisp. It prematurely optimizes and constrains your thinking into a single paradigm.
- abrax3141 7y agoI write CL production code all the time. Basically daily. I use hash tables a lot, and those are obviously mutable (I did mention that as an exception) but most of the rest of my code is pretty much functional, which is generally (though not uniquely) immutable. I can’t really see how it makes sense to do hash table immutably. It might just be me, but functional style tends toward immutability be default.
- DannyB2 7y agoYou CAN create mutable data structures. This can be very useful LOCALLY. But then turn it into an immutable data structure as soon as you return it. Example: a sieve function for primes. Pass it a range of n1 to n2. It seives that range, say from (1 million) to (1 million plus 100,000). It could use an efficient mutable data structure locally. Then return an immutable result. Especially since the result has no reason to EVER be mutable again. You're not going to find more or less primes within that range.
- DannyB2 7y agoIt's not just about immutability. it's about the fact that you can do mutations producing a cheap copy of the data structure. If I have an array of ten million numbers. I can alter one of those array elements, getting back a new immutable array with the modification. And fast. Yet the original array still remains without the modification. How the implementation magic works is not a concern when thinking about the abstraction. But you can read how the magic works and like any magicians trick, you end up saying "oh, so that's how it's done". So you can modify one array element and get the performance (extremely close) to how you expect a single array element modification to perform. You the original copy of the entire array also remains if some other code has a copy of it. Imagine a recursive algorithm searching a game board with different move possibilities. Apply a move to a board and get a new game board. Yet the original game board remains unaltered. And you didn't have to implement any magic in your search algorithm. No copying of the entire game board. You just get your new game board with the new move applied so that your recursive function can proceed.