Hi Liutauras,
Some answers were posted while I was writing mine, so some ideas might be duplicated!
Your first question is legibility, but it also concerns ease of debugging. Your example could be rewritten as:
This generally helps imperative programmers to understand what's happening because it looks like a sequence of operations.
Another benefit is that it is easier to put breakpoints on each separated part. Although a smart debugger will allow you to select on which part you want to put the breakpoint even when the code is on a single line, you might have trouble to later remember which part of the line the breakpoint applies to.
The main benefit of the functional implementation is that the code only shows what is done, and not how it is done. The imperative way exposes the loop as a mean to iterate over a collection, and the conditional instruction as a mean to
test for a condition. It is easy to mess with these details. In the functional approach, these details are abstracted, so you can't mess with them. By the way, the same kind of abstraction was use to switch from indexed loop to the "for each" version. Functional programming pushes abstraction much further.
In the same way, this approach does not expose the
Set, and it might also produce an immutable set, which of course makes the code safer. It is safer than good imperative code, but moreover, it is safer that bad imperative code that is so easy to produce, such as:
This example is a bit contrived, but you see the point. With loops, one can always close over some variable in enclosing scope, even sometimes inadvertently. The functional way protects us against this.
Regarding your second question, it is, of course, better when a team agrees upon a preferred approach. But it is not always necessary. Many functional techniques may be used without breaking so-called "best practices". In fact, we should consider different cases:
- Several programmers working on the same code at the same time should probably agree about what type of code they write.
- Working alone on some code means your code might eventually be maintained by others. I such case, nonstandard techniques should be thoroughly documented to ensure the that the maintainers will understand your code. (Or to ensure that you will understand your code some years later!)
- Working on a library that is used by other programmers in your team is a different case. For the implementation, the rules above apply. But for the API, it is a different problem. You have to use the types that your users are expecting. Returning unevaluated results, for example, would probably not be an option. In Java, this would correspond to returning a Supplier<A> instead of an A, or returning a Stream instead of a List. (Returning streams is probably not a good idea in any case, but for other reasons!). Of course, returning unevaluated effects would be even worse since your user would generally not expect your code to return anything, but to evaluate the intended effect.
Regarding the third question, my personal choice is to always use a functional implementation. I do not care about it being the fastest possible implementation. It just has to be fast enough. Unfortunately, how fast the code has to be is rarely part of the specification. If the code happens to be too slow, I consider switching to an imperative implementation as an optimisation, but I would as often as possible wrap it in a functional interface and avoid making the imperative aspects visible from the user.