items.filter(item => item.isSelected)
is not “functional trickery”.• `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.
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.
let show_items = items.filter(…)
But yeah, things like this you have to know in advance anyway.Because if it is not functional trickery, I don’t really know what is (and no, recursion is not the answer) and it is sure clearer than the 3 nested loop or whatever one might come up with.
But I also appreciate Go as a language. I love the wide standard library, I love how asynchronous IO is handled, I love the tooling and the fast compilation, I love the cross-compilation. And more than that, I recognize them as good solutions to problems. I wish other languages would adopt that. I wish making a web server/API in OCaml was as easy as in Go. I wish Rust had some M:N threading model when doing stuff that's a bit less performance-sensitive. I wish Stack/Cabal/Nix were as easy as go build. When I had to make a small program to cut text files for a friend, I did it in Go, and it was a great experience.
We're engineers, let's try to be a bit less tribalist and a bit more pragmatic, and we will all end up with better tools.
I'm not the GP, but I think this is off the mark. There's a big difference in purposefully choosing boring technology and purposefully taking pride in not knowing things.
"This is why I enjoy Go, it shepherds you to write clear and uniform code that cannot be mistaken. Go makes well-known tradeoffs that have already been discussed to death, and I find that I agree with these tradeoffs. In my experience, collaborating on Go code is easier as the absence of some means of abstractions prevents people from creating their "language inside the language", and thus allow for a team to share more easily their mental model about how the code works."
You could even add a caveat in the form of "Go isn't perfect, no language is, and has made what could be considered objective flaws. But on the other hand, the handling of things like generics shows that the maintainers are not closed to idea. They tend to err on the side of taking more time to make things rights, even if that time isn't always needed, and that's a tradeoff I agree with.".