I was asked why one should prefer
Here’s an analogy: why do we, as a talking species, use different layers of vocabulary? Why do we have abstract terms in our language, as opposed to only concrete terms drawn from tangible things from our physical world? I mean, the Romans did fine without abstract language, right? Or were they limited by Latin? Wasn’t the incredible boom of philosophical thought during the Enlightenment facilitated by the abstraction powers of the German language?
Here’s a different frame: why do we have programming languages of a higher level than Assembly? What’s wrong with
CMP AL,10; JNZ loc?
map and friends are more precise, sophisticated ways to talk about consistent patterns in data manipulation. Using them over
for is analogous to using the word “cake” instead of “the kind of food that you make by whipping egg whites and maybe adding sugar”. Interestingly, you can eventually add new layers of category on top of established layers: just like saying that butter cakes constitute a specific family of cakes, one could say that
pluck is a specialization of
for can still be useful, just like
goto is not necessarily the devil (we still use
switch, which is a
goto in disguise). It’s just very low-level, and for most scenarios we have well-known higher-level terms. Just as we are continuously taught vocabulary and expression over the course of our school years (and hopefully beyond that) to improve our communication skills (as speakers and as listeners), together we can and should aim to develop the fineness of our expression as programmers.
Caveat: yes, there is such a thing as too much abstract language, or a language too sophisticated, and it is important that speakers adjust their discourse to the expected reader. Just like in natural languages. But if our primary purpose in any given project is to develop quality products with quality codebases, we can raise the requirements in terms of language comprehension — within reason.
OK, but that’s all theory. If both
map are used with an inlined callback, pragmatically speaking, how do they represent a different level of abstraction? To trained eyes, they pretty much look the same, and neither forces me to avoid side effects.
filter you are intentionally limiting your power. You establish a contract, wherein you say:
- you’re not going to mutate the collection;
- (in the case of
map) you’re going to return a collection with the same size and with data derived from the original collection’s individual items;
- (in the case of
filter) you’re going to return a subset of the original collection, preserving the items and the order.
These important statements are part of the abstraction. As for the side effects:
filterprevent you from yielding side effects by default, whereas the default with
foris to mutate, unless you have an additional statement to create a new empty collection beforehand;
forinherently requires more noise to be added, as you need to set up the conditions of the loop and handle the iterating variable. Noise dilutes intent. Noise also makes it easier for mistakes to slip through. Anything from a misspelled
array.lenght, to a rogue comma or an illogical condition — ultimately, those are technicalities that you didn’t actually need to care about in the first place, yet they lead to pesky bugs creeping in.
Stock photography: Annie Spratt