6 ms·
Immutability is very helpful, and the connection between values and identity is illuminating. But there have been very important developments in programming la
by stuhood 7y ago
Immutability is very helpful, and the connection between values and identity is illuminating.
But there have been very important developments in programming languages since this post/page was written: notably, the introduction of "borrow checking" (exemplified by Rust's implementation). Borrow checking has a very significant positive effect on the sustainability of imperative code, which makes the claim that "imperative programming is founded on an unsustainable premise" feel dated.
It is worth taking the time to understand what borrow checking enables. For example: borrow checking allows even mutable datastructures to be treated as values with structural equality. It does this by guaranteeing that unless you have exclusive access to something, it may not be mutated.
A good explanation of the benefits of ownership and borrow checking: http://squidarth.com/rc/rust/2018/05/31/rust-borrowing-and-ownership.html http://squidarth.com/rc/rust/2018/05/31/rust-borrowing-and-o...
- stingraycharles 7y agoBorrow checking and immutability solve two different problems. Immutability is about the absence of ownership and state, while borrow checking is a way to manage ownership. One does not replace the other, they coexist solving different problems.
- louthy 7y agoExactly, it's like the integer 5, nobody owns the number 5, nothing borrows the number 5, it just exists as a value that anything can use (and trust to always be the same). When immutable values (whether it's the number 5 or a record, or whatever) are used with a pure function then the returned value can be essentially a direct replacement for the function invocation, making the operation essentially take zero time (i.e. there aren't intermediate mutation states, locks, or any other coordination). This is the point that Rich Hickey suggests in a number of his talks [1] - which are always excellent IMHO. [1] Are We There Yet - https://www.youtube.com/watch?v=E4RarTAZ2AY https://www.youtube.com/watch?v=E4RarTAZ2AY
- stuhood 7y ago> When immutable values (whether it's the number 5 or a record, or whatever) are used with a pure function then the returned value can be essentially a direct replacement for the function invocation, making the operation essentially take zero time (i.e. there aren't intermediate mutation states, locks, or any other coordination). The same can be said of any variable in Rust that is passed to a function via immutable reference. Borrow checking guarantees (at compile time, with no runtime overhead) that even if a user passes something that supports mutation (such as a BTree or an array) to a function, that that function may not mutate unless it has permission to. This means that if you have an immutable reference to something, it can be treated as a value. For example: a Vec or BTreeMap (structures that supports mutation) can safely and easily be used as Map keys in Rust, because borrow checking guarantees that once the Map has taken ownership of the structure, it "is a value": ie, is immutable.
- 0815test 7y ago> The same can be said of any variable in Rust that is passed to a function via immutable reference... I don't think this is correct. Rust does not really have immutable references, it has shared references vs. exclusive ones. Shared references are ordinarily immutable, but controlled mutability can be reintroduced (directly or indirectly) via a variety of type constructors (Cell<>, RefCell<>, Mutex<> etc.). Hence something that is borrowed immutably cannot be assumed not to change in the general case-- unless one also takes care to work with these mutability mechanisms as needed, which cannot really be done in a "fully general" way. Rust developers seem to have realized that actual immutability is not that easy, and that the guarantees that they do offer may be more appropriate.
- deleted 7y ago[deleted]
- stuhood 7y agoBorrow checking allows even structures that support mutation to be safely (checked by the compiler) treated as immutable, and thus as values. Clojure also recognizes the connection between ownership and mutability in its "transients": https://clojure.org/reference/transients https://clojure.org/reference/transients ... compile time borrow checking extends that idea to an entire language.
- casion 7y agoWhat do transients have to do with ownership? They are simply a way to gain new performance characteristics from an existing data structure.
- stuhood 7y agoThe thing that makes it safe to use mutation in the context of a transient is that you can know with certainty that you have exclusive access to the value (because no other viewer has observed it yet). This is also what borrow checking can guarantee: except in significantly more positions in the code, and at compile time rather than runtime.
- truth_seeker 7y agoNice explanation
- namelosw 7y agoNot totally different, with strict immutability enforced for the whole language there's no need for borrow checking. Because there's no identity at all. You don't have to borrow number 3 because another number 3 is the same 3 as any other 3.
- wilsonthewhale 7y agoYup, this is the reason why both Rust and Clojure are my favorite languages. The same problem (concurrency), with solutions pretty much on opposite ends of the spectrum. One with pervasive immutability and the other with static checking.
- andreareina 7y agoOne of the reasons that immutability gets pushed is that it solves concurrency's shared mutable state problem, which is exactly the same problem that borrow checking is meant to solve.
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]
- sbov 7y agoRust doesn't let you have a mutable reference while you have immutable ones. It's only a problem in Rust because mutating the underlying data would cause problems for chunks of code using the immutable data. But in Clojure it's not a problem. I find lots of applications where working with stale, consistent data is just fine. Also, if you ever need to rewind your state Clojure's immutability makes it trivial. I've used this in e.g. latency compensation for multiplayer games.
- skybrian 7y agoIf you choose to create immutable snapshots of your objects, I imagine the same could be done in Rust?
- didibus 7y agoThe borrow checker comes with different trade-offs though, which I personally find, for many applications, the immutable data-structure + atomic references with GC route (or values over time semantics) is a better fit. The way you are forced to model things so that the borrow checker can successfully validate your code can often be frustrating, and it also comes at the cost of slow compile times and interactive programming, which Clojure is kind of a champion at, so I'm not sure it would be a good fit to add a borrow checker to it. I love the borrow checker for manually managed memory though, and for programs that need that kind of performance and tight memory usage, it is great. I've been following along the development of Carp because of that and my preference of Lisps: https://github.com/carp-lang/Carp https://github.com/carp-lang/Carp
- keymone 7y agoSo if I have mutable array in rust in state A and I start writing state B i to it from the back end in one thread, and reading the state A from the front in another thread - am I guaranteed to read actual state A or will I read a mix of A and B?
- arve0 7y agoThe borrow checker does not allow that, it will complain "error[E0502]: cannot borrow `vector` as immutable because it is also borrowed as mutable".
- keymone 7y agothat in my mind is classic example of how immutable approach saves you from the problem of reading inconsistent world. just like assembly registers, cache lines or struct field alignment, having to think about possibility to read the world inconsistently should not be "generic engineer's" job. it should be considered low level. there are more important things my brain has to be occupied with - like doing my actual job of writing business logic. it's great that borrow checker will error out, but i just don't ever want to see an error like that and that's why i love clojure and immutable persistent datastructures in general.
- arve0 7y ago> but i just don't ever want to see an error like that Then, do not mark your variables as mutable. This error only happens if you use `let mut variable_name = ...` AFAIK, `let var`, e.g. immutable variables, behaves the same as in clojure.
- keymone 7y ago"anything that is legal according to compiler will eventually make it's way into the codebase" (approximately quoting Carmack here) default behavior should be safe and simple to use, dangerous behavior should be hard to reach and inconvenient to use.
- amw-zero 7y agoThis is a very great point that I’d like to highlight. The main problem with languages of today is shared mutable state. It’s the default in every major language, and it is the wrong default because of the inherent complexity it brings. You can avoid this complexity by killing either one of the adjectives: shared OR mutable. Immutability is generally pitched as the only way to solve this problem, and it is definitely a valid solution, though not the only one. The other dimension to cut off is sharing. If mutable data is not shared, its mutability also becomes mostly irrelevant. Rust and Swift both support immutability as well as unshareable references. In Rust’s case, it’s more like organized sharing that prevents simultaneous mutation. In Swift’s case, structs truly can only have one reference.
- didibus 7y agoI'm not sure exclusive access is enough in all cases though. That's why immutability is still a strong default in Rust and Swift. The classic example is calling a method on an object which will mutate its content without the caller knowing. That's why I'd still favor immutability if performance doesn't preclude it.