Let’s play devils advocate; does it return a filtered list, does it change itself or both?
As clear as it may look at first, I still would have to look in the docs to find out.
In some languages, especially many ahead-of-time compiled languages, this will be a part of the signature so that it’s at least much more difficult to get wrong¹. In Rust, for example:
• `fn filter(self, P²) -> A` consumes self and returns a new thing of type A;
• `fn filter(&mut self, P)` mutates in-place;
• `fn filter(&self, P) -> B<'_>` doesn’t mutate self, but produces a new thing of type B containing references to the contents of self.
As a concrete example, Rust has `Vec::retain(&mut self, P)` to filter a list/array in place, and `Iterator::filter(self, P) -> Filter<Self, P>` to filter an iterator, consuming that iterator object and wrapping it in another.
This is a sterling example of the point of the original article: Rust pushes you to thinking about ownership, and it’s really great and I miss it when working in other languages, where I have to stop and think whether I’m allowed to mutate this object or whether I should make a copy, and the cost of getting it wrong is bad performance in one direction and bugs that can be a nightmare to diagnose in the other direction.
—⁂—
¹ You need linear types to make it just about impossible to get wrong by accident. Rust has a weaker substitute that you can annotate functions with, #[must_use], which says “warn the user if they neglect to use the return value, because they’re almost certainly doing the wrong thing”.
² I’ve omitted the filter predicate P from the signatures to reduce distraction; in each case it’d probably be something like `<P: FnMut(&Self::Item) -> bool>`: a function that is passed a reference to an item and returns true or false.
You could make the same arguments when you're working with slices in Go. Take append(slice, element). Does it return a slice with the element appended to it, or does it modify the slice in-place?. For some things, you have to look at the docs, that's how it is. Usually languages have conventions around that. In JS, map, filter and reduce always return a fresh array and leave the initial array untouched.
> You could make the same arguments when you're working with slices in Go. Take append(slice, element). Does it return a slice with the element appended to it, or does it modify the slice in-place?
A funny trick question as it pretty much does both.
Also I'll take "functional trickery" over needing a page called Slice Tricks so you can find out how to delete an item any day of the week.
• `fn filter(self, P²) -> A` consumes self and returns a new thing of type A;
• `fn filter(&mut self, P)` mutates in-place;
• `fn filter(&self, P) -> B<'_>` doesn’t mutate self, but produces a new thing of type B containing references to the contents of self.
As a concrete example, Rust has `Vec::retain(&mut self, P)` to filter a list/array in place, and `Iterator::filter(self, P) -> Filter<Self, P>` to filter an iterator, consuming that iterator object and wrapping it in another.
This is a sterling example of the point of the original article: Rust pushes you to thinking about ownership, and it’s really great and I miss it when working in other languages, where I have to stop and think whether I’m allowed to mutate this object or whether I should make a copy, and the cost of getting it wrong is bad performance in one direction and bugs that can be a nightmare to diagnose in the other direction.
—⁂—
¹ You need linear types to make it just about impossible to get wrong by accident. Rust has a weaker substitute that you can annotate functions with, #[must_use], which says “warn the user if they neglect to use the return value, because they’re almost certainly doing the wrong thing”.
² I’ve omitted the filter predicate P from the signatures to reduce distraction; in each case it’d probably be something like `<P: FnMut(&Self::Item) -> bool>`: a function that is passed a reference to an item and returns true or false.