This article gets at the crux of the difference between what I think of as the OO and FP mindsets: OO mindset is overly zealous about applying 'encapsulation' and 'information hiding'. An OO enthusiast might say that revealing your internals in this way is bad design, and if you need it you should 'encapsulate' the 15% and 4% solutions in their own (Helper, ugh) classes. The extreme-radical FP view is to ask why we need keywords like private at all. Why not just show functions that are easy to use and harder to use, and let callers decide what works best for them.
As you can infer from my use of scare quotes, I side with the author on this after accumulating repeated scar tissue.
> Why not just show functions that are easy to use and harder to use, and let callers decide what works best for them.
Won't you be stuck exposing those APIs for the lifetime of the product or risk breaking backwards compatibility?
What if you could make the code significantly faster by changing the parameters you pass in to an internal function? What if you discover a bug that you can only change by changing a method's signature? What if you just want to refactor? Doesn't exposing your internals prevent you from doing these things?
Won't you be stuck exposing those APIs for the lifetime of the product or risk breaking backwards compatibility?
A real revelation for me over the last year of using nodejs more and more, is that every component can use its own dependencies, at exactly the versions it requires. So, when you add to an API, bump minor. When you take away, bump major. Either way, you can bump patch for bug fixes, on any major-minor combo, whenever you want. So "stuck" isn't really possible any more.
You run the risk of it, sure. However, early mover advantage is quite large in all things. Including finding the right abstractions. To paraphrase a witty quote, how do you know the correct abstractions to make? By making the wrong ones.
> The extreme-radical FP view is to ask why we need keywords like private at all.
I'd say this isn't quite true. FP is full of sophisticated ways to hide information. Done correctly it is a giant boon to composability—often done under the name "modularity".
The core of this function resides in type quantification—existential or universal. Any polymorphic type system will thus have a small amount of this. In particular, the fact that a function is parametric in a type is exactly an expression of the fact that a function remains ignorant to all details of the type actually chosen:
id :: a -> a -- a function with this type cannot examine its argument
id x = x
More tangibly, ML-family languages (but not quite F#, I don't think) have the ability to create abstract types by exporting module signatures which are ignorant to the actual type embodied in the struct. Again, this is a powerful technique for increasing composability.
The core idea in each case is that when you build interfaces which demand exactly and only what they truly need from their impementers then you allow the maximal variety of implementers possible. You also allow the implementer to be exchanged---your use of this interface thus composes in more contexts.
This is exactly what I was trying to say in my comment regarding composability/orthogonality in FP. You've presented the point much clearer than I did : )
I think that this isn't a symptom of OO vs FP, but rather of the types of designs that arise from OO and FP systems. In my mind, it boils down to composability and orthogonality. The quote that comes to mind is:
"It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures."
I think OO tends to go with the 10 functions on 10 data structures, whereas the FP goes with 100 on 1. Once you are able to abstract your API in such a way, the work of creating these 'multiple levels of abstractions' becomes an exercise for the reader, because of the composability of the API.
As you can infer from my use of scare quotes, I side with the author on this after accumulating repeated scar tissue.