The fact that functional programming emphasizes "abstraction" so much means that, by definition, you're further away from what's actually happening. So looking at some declarative code it's not immediately obvious what the computer will actually do. The kinds of programs I write require me to care about that deeply. So when I see declarative code all I see is a giant question mark as I wonder what in the world the computer will actually be doing when it executes that code.
Anyway, my point isn't that one is better than the other, it's that it depends on what you're doing. For some of us, the higher levels of abstraction are actually less readable.
But I would argue that the majority of Go programs should not require the programmer to follow the intimate details of a program's execution when trying to understand the business logic. The fact that the language has a garbage collector would suggest that it was designed for programs where some details can be hidden away.
So I agree with your general point, but I am somewhat skeptical about its application to Go.
Also, it’s kind of laughable to think that writing code in a high level language with a runtime, GC, etc, you have any idea what will happen at execution.
It could still be better though if you have, for example a number of pipelines for your data. There, the reuse of Sort/Group/Frobnicate/... in close by blocks would actually be nicer.
But yeah - the more extra syntax is required for those pattern, the worse they are in practice. (or, the more things you have to abstract in a small range to make it useful)
1. Both examples contain a bunch of extra logic that has nothing to do with the difference between the two styles. For example, the code that converts users to that `UserLevelPoints` struct takes up a lot of space in both examples and is essentially the same in both. I think it would have been better (for pedagogical purposes) to just return the users (or perhaps a simpler struct containing a level and a user).
2. Both examples still have all the verbosity that is typical of imperative code, since it's still Go after all.
If one were to write the same function in a syntax which was designed for functional programming, the result might look something like this:
topUser = head . sortBy points
topUserPerLevel = topUser . values . groupBy level
I find this vastly quicker to read and understand than either of the examples in the article. But that relies on me knowing the functional-optimized syntax (e.g., that the `.` operator is just function composition), and for most imperative programmers that is the main hurdle. Since I am familiar with both styles, I can tell you that this would take me about 6 seconds to understand, whereas understanding each of the examples in the article took me at least a minute to even read the code, let alone understand it—and if there were bugs I wouldn't have noticed.In contrast, with the simple functional version I provided, I can read it in seconds and be reasonably confident that it is correct (assuming all the pieces fit together in a valid way, which a type checker can check for me).
If the unfamiliar syntax prevents you from grokking the example I provided, it's reasonable to assume it's just some clever code golf that should be discouraged in production code. But please resist that temptation; this is actually what good code looks like. It's simple, not repetitive, and there aren't many places for bugs to hide.
In your opinion.
> Generally, functional programs are safer and shorter than their procedural/OOP alternatives
Please provide any evidence that this is true, especially the ‘safe’ part.
Any study I’ve read says there’s almost no effect on language choice at all on bug rate.
> In your opinion.
Correct. But my opinion is informed by years of experience with functional programming, procedural programming, and OOP (each).
> Please provide any evidence that this is true, especially the ‘safe’ part. Any study I’ve read says there’s almost no effect on language choice at all on bug rate.
Sure, here is a paper which provides the evidence you're looking for: "A Large Scale Study of Programming Languages and Code Quality in Github" (https://dl.acm.org/doi/10.1145/2635868.2635922). That paper concludes:
> The data indicates functional languages are better than procedural languages; it suggests that strong typing is better than weak typing; that static typing is better than dynamic; and that managed memory usage is better than unmanaged.
I personally don't trust academic studies on programming language effectiveness, since they often contradict each other (e.g., https://arxiv.org/abs/1901.10220 contradicts the paper I cited above). But if you're looking for a peer-reviewed paper, there you go.
Choosing the right paradigm absolutely has an effect on the bug rate. If you have to write more code, repeat code, or deal with low-level details irrelevant to the problem at hand, you are going to make more mistakes. On top of that, functional languages often have better type systems than procedural/OOP ones, which helps with bug catching that much more. Taking this to extreme, with dependent type systems you can actually verify arbitrary mathematical properties of your programs.
How much experience do you have with functional programming? I have professional and academic experience with both functional programming and OOP, and everyone I've met with that experience agrees about the trade-offs I've been discussing in my comments.