This is equivalent to that for people who are irrationally terrified of mutability, and are willing to abandon performance.

Counterintuitively Julia recommends the use of immutable data types for performance reasons, because immutability enables more flexible compiler optimisations

An immutable variable can be savely shared across functions or even threads without copying. It can be created on the stack, heap or in a register, whatever the compiler deems most efficient.

In the case, where you want to change a field of an immutable variable (the use case of lenses), immutable types may still be more efficient, because the variable was stack allocated and copying it is cheap or the compiler can correctly infer, that the original object is not in use anymore and thus reuses the data of the old variable for the new one.

Coming from the C++ world, I think immutability by default is pretty need, because it enables many of the optimisations you would get from C++'s move semantics (or Rust's borrow checker) without the hassle.

There is nothing counter-intuitive or julia-specific about it:

Fastest way is to have your datastructure in a (virtual) register, and that works better with immutable structures (ie memory2ssa has limitations). Second fastest way is to have your datastructure allocated on the heap and mutate it. Slowest way is to have your datastructure allocated on the heap, have it immutable, copy it all the time, and then let the old copies get garbage collected. The last slowest way is exactly what many "functional" languages end up doing. (exception: Read-copy-update is often a very good strategy in multi-threading, and is relatively painless thanks to the GC)

The original post was about local variables -- and const declarations for local variables are mostly syntactic sugar, the compiler puts it into SSA form anyway (exception: const in C if you take the address of the variable and let that pointer escape).

So this is mostly the same as in every language: You need to learn what patterns allow the current compiler version to put your stuff into registers, and then use these patterns. I.e. you need to read a lot of assembly / llvm-IR until you get a feeling for it, and refresh your feelings with every compiler update. Most intuitions are similar to Rust/clang C/C++ (it's llvm, duh!), so you should be right at home if you regularly read compiler output.

Julia has excellent tooling to read the generated assembly/IR; much more convenient than java (bytecode is irrelevant, you need to read assembly or learn to read graal/C2 IR; and that is extremely inconvenient).

It's a similar idea to map() but for more complex objects than arrays. When people use "map" in Javascript (or most any other language that supports it) do they do so because "they are terrified of mutability, and are willing to abandon performance?"

Your comment reads like the response of someone who is struggling to understand a concept.

Only the get half is `map`-like. In combination it's more like a property descriptor, which is far easier to understand and much more efficient.

And, if it wasn't obvious, it's only the `set` half where lenses suck for performance.

Immutability gives you persistence, which can be practically useful. It’s not just fear.

Yes. O(1) snapshots are awesome! Persistent datastructures are a monumental achievement.

But that comes at a performance price, and in the end, you only really need persistent datastructures for niche applications.

Good examples are: ZFS mostly solves write amplification on SSD (it almost never overwrites memory); and snapshots are a useful feature for the end user. (but mostly your datastructures live in SRAM/DRAM which permit fast overwriting, not flash -- so that's a niche application)

Another good example is how julia uses a HAMT / persistent hash-map to implement scoped values. Scoped values are inheritable threadlocals (tasklocal; in julia parlance, virtual/green thread == task), and you need to take a snapshot on forking.

Somebody please implement that for inheritable threadlocals in java! (such that you can pass an O(1) snapshot instead of copying the hashmap on thread creation)

But that is also a niche application. It makes zero sense to use these awesome fancy persistent datastructures as default everywhere (looking at you, scala!).