This is actually an area where there’s room to improve FP languages.
If you track ownership in functional languages, you can statically determine if a value is used more than once.
If it’s only used once, you can apply any updates to that value in place without allocating more memory.
This gives the performance benefits of mutability with the safety benefits of immutability, in some common cases.
The main trick is adjusting memory layouts accordingly. You can keep it simple by only applying this optimisation for functions of A -> A, or if you’re replacing it with a different type you can analyze the transformations applied to a value and pre-emptively expand the memory layout.
If a value is likely to be used only once, but might be used multiple times, you can also apply the same approach at runtime by reference counting and updating inplace when there’s only a single reference (for functions of A -> A at least).
I believe the Roc folks are aiming to have aspects of this functionality, and I also believe there’s similar stuff becoming available in Haskell under the guise of linear types.
Finally, if you really need a shared mutable value, that can be achieved with mechanisms like the State type in Haskell.
In short, the pieces are there to create a functional programming language that doesn’t introduce needless memory usage overhead, but I don’t think anyone has put all the pieces together in a convenient and accessible way yet.
I get that not everyone does, but a large part of why I use Clojure is because it makes a whole class of concurrent designs easier. In particular, sharing that immutable data across multiple threads.
As a simplified example: one thread modifies the data, and another thread writes a snapshot of the data.
In the programs I write, it would pretty much never benefit from this optimization.
If you track ownership in functional languages, you can statically determine if a value is used more than once.
If it’s only used once, you can apply any updates to that value in place without allocating more memory.
This gives the performance benefits of mutability with the safety benefits of immutability, in some common cases.
The main trick is adjusting memory layouts accordingly. You can keep it simple by only applying this optimisation for functions of A -> A, or if you’re replacing it with a different type you can analyze the transformations applied to a value and pre-emptively expand the memory layout.
If a value is likely to be used only once, but might be used multiple times, you can also apply the same approach at runtime by reference counting and updating inplace when there’s only a single reference (for functions of A -> A at least).
I believe the Roc folks are aiming to have aspects of this functionality, and I also believe there’s similar stuff becoming available in Haskell under the guise of linear types.
Finally, if you really need a shared mutable value, that can be achieved with mechanisms like the State type in Haskell.
In short, the pieces are there to create a functional programming language that doesn’t introduce needless memory usage overhead, but I don’t think anyone has put all the pieces together in a convenient and accessible way yet.