71 karma · joined March 4, 2022
I once spent a full day debugging a problem that came down to the implementation details of .zip. The author had assumed that .zip would add extra null elements to the output array if the inputs didn't match in length, which is sadly not the behavior of our programming language. We determined this was the bug after breaking out the REPL and running line by line because it was hard for us to visualize exactly what was happening in functional methods like these (there were more around the .zip call). We ripped out the .zip and turned it into an explicit for loop because we wanted the behavior for the case of "these arrays are different length" to be extremely obvious to the reader. The author and myself probably learned that zip had this behavior at some point, but its terseness hid a ton of nuance in code review that we decided we cared about later on in a way that explicit looping did not.
So, I get that internalizing functions like this can reduce cognitive load in some cases and it's certainly shorter. However, I spend a large percentage of my time looking at code where small semantics like the above matter a great deal. What happens if the length of this array is less than 20? What is the default return value if there are no items: None or 0? When I loop over something, it's often extremely important -- something that operates on an absolute ton of elements, altering its behavior is a big change in business logic type of stuff. Too often we've run into edge cases like the above that just flat out need to be explicit.
I think if I were working on smaller teams/codebases with more homogeneous experience levels I might feel differently. On my current team I will take the tradeoff of "the average function takes a bit longer to parse" if it means that everyone can reason about any given bit of code without trouble. We're trying to minimize how bad things can be, e.g. never have multiple programmers sitting around a computer trying to figure out what the bug is in a nested list comprehension with gratuitous use of function chaining. We use Go over other languages nowadays because we believe that for our team, explicitness results in the lowest cognitive burden on a global level. I think it's fine that other languages make other choices -- sometimes I program in Haskell for fun -- but if I come back to something I wrote long ago, I always break out the manual to remind myself what exactly certain expressions do.
I was reflecting on this and the one language where I don't feel this way is SQL. I'd consider it my strongest language, and it's an extremely functional language. I have no trouble deciphering complex SQL, but it takes me an enormous amount of time to figure out what happens in those long function chains in general purpose programming languages. I'm not sure what it is about SQL that makes me feel this way, but maybe it gives some hint why our brains seem to work in different ways?
In my opinion the Go code is dead simple and unambiguous with no nuance hidden away in methods whose exact semantics I certainly don't have memorized. I'd have to look at 3 different function signatures to figure out what those Ruby methods do every single time I was reviewing code like this.