I think there is more going on here. For the sake of eliminating an if, you are lifting everything else into what is effectively a separate language with the original code embedded in that language. Overall, that doesn't look like a win to me.
After all, what is the actual domain logic? Is it flatMap().map().flatMap().map()? Or is it validate().businessLogic().generate() ?
That doesn't mean that what is going in isn't useful, but it seems to me we need to have a way to specify the lifting without writing it down everywhere, so that the actual code can be expressed at the base level again.
for(blah blah blah)
tells the reader nothing more than there's going to be a loop, but map(blah blah blah)
tells the reader one sequence is going to be transformed into another by applying a function to each item. That's more informative to the reader if used for its intended purpose. It has the opposite effect if abused to repeatedly call a function mainly for its side-effects.All programming techniques should be viewed as means to write code that's some combination of readable, reliable and performant, not as ends themselves.
Except in this case it wasn't actually a sequence, but an Optional/Either and it wasn't about transforming that sequence but about error handling. So in a way the code wasn't even not intention-revealing, it was actually deceptive.
I don't use partial application/currying that much, but more and more over time.
All in all I feel that doing these things have greatly improved my code and efficiency, and I'm kind of waiting for some downtime to get back into learning the more 'hardcore' functional languages.
It might be said that any such example would be too complex and drawn-out for an article like this, but if so, then to me that is like the drunk looking for his keys under the lamp-post: writing an article that claims a simple example shows a profound difference, when it does not, suggests that the Emperor's wardrobe is threadbare. The thing to do is to provide links to articles where this is worked out in sufficient depth, and I would like to see some of those links here.
* Mapping (producing a data structure of the same size/type)
* Filtering (producing a data structure of the same type buy smaller)
* Side effects
* Collecting (producing a different data structure)
If you're incredibly unlucky or foolish a for loop might be doing more than one operation at a time!
Assuming you're only using the return types, map, filter, and collect are very clear about having only one purpose.
If that's not telling you more I don't know what is.
Whenever I write Java (or Java-like OO), I always have this exact "separate language within a language" feeling.
I'm supposedly working in a high-level, object-oriented, loosely-coupled, message-passing/dynamically-dispatching world of classes and instances; yet an awful lot of code is actually written in a separate language of "primitive values" with opaque control structures like if/then/else, for/while, etc.
Compare this to e.g. Smalltalk, where "ifTrue" is a method on boolean objects, "timesRepeat" is a method on integer objects, etc.
Having "for" not be a method on an object means it's something completely different: a magical keyword control structure, a gift from the irreproachable language designers to the lowly language users; since mere users cannot be trusted to make such decisions for themselves.
Is the potential of failure part of my domain? Or is it something I have to handle in order to avoid errors?
Should .businessLogic() have to handle the case if .validate() failed? Or should it only be called if validate succeeded?
Using a common API (Either is not new or novel, it's all over the place) to separate success from failure lets me write code that is only concerned with its side of the success/fail tree without polluting it with null checks and error handling that belongs elsewhere.