How I would summarize the article is that mutation should be confined to internal objects that have not been published yet or are temporaries that are thrown away.
Theoretically, you can make every API mutate data. If the user didn't want to mutate data, they can explicitly make a copy first. Whereas if there only exists an immutable API to return new objects, then it's hard or impossible to get the benefits of mutating in place.
As far as I know, only Rust, C++, and C give support at the type-checking level to denote which functions will modify the interior of their arguments. This way, you can distinguish behaviors easily - for example in Rust:
impl Vector {
// The method reads this current vector and returns a new one.
pub fn normalize(&self) -> Vector { ... }
// The method reads and modifies this current vector in place.
pub fn normalize(&mut self) { ... }
}
The article gave this subtly flawed example in Python:
def normalize_in_place(array: numpy.ndarray):
low = array.min()
high = array.max()
array -= low
array /= high - low
def visualize(array: numpy.ndarray):
normalize_in_place(array)
plot_graph(array)
data = generate_data()
if DEBUG_MODE:
visualize(data)
do_something(data)
In Rust, it would be a type error because visualize() takes a reference but normalize_in_place() takes a mutable reference:
fn normalize_in_place(array: &mut numpy::ndarray) {
let low = array.min();
let high = array.max();
array -= low;
array /= high - low;
}
fn visualize(array: &numpy::ndarray) {
normalize_in_place(array); // ERROR
plot_graph(array);
}
let data: numpy::ndarray = generate_data();
if DEBUG_MODE {
visualize(&data);
}
do_something(&data);
> As far as I know, only Rust, C++, and C give support at the type-checking level to denote which functions will modify the interior of their arguments.
1. C and C++ allow casting away constness, which may or may not be UB depending how the parameter is defined
2. Swift also provides this ability to an extent: struct parameters have to be flagged `inout` to be mutable
Theoretically, you can make every API mutate data. If the user didn't want to mutate data, they can explicitly make a copy first. Whereas if there only exists an immutable API to return new objects, then it's hard or impossible to get the benefits of mutating in place.
As far as I know, only Rust, C++, and C give support at the type-checking level to denote which functions will modify the interior of their arguments. This way, you can distinguish behaviors easily - for example in Rust:
The article gave this subtly flawed example in Python: In Rust, it would be a type error because visualize() takes a reference but normalize_in_place() takes a mutable reference: