I work in FP languages, and I find this type of code extremely intuitive and easy to understand.
I suspect that you are just not used to thinking this way, and that with more familiarity, you would not have this opinion.
But this is not unique to FP. If I'm using OOP, I can do the same thing with virtual functions. In UI frameworks I can do the same thing with callbacks.
Frankly, I don't like it, whether it's FP or not.
The complexity of higher order functions can easily be reasoned about by looking at the cardinality of the type. Sum types and product types are standard and easy to reason about.
However an exponential type that takes in an exponential type and returns another exponential type leads to cardinalities and possibilities that are much harder to reason about.
Composition of combinators is a pattern promoted by point free programming that leads not only to higher readability but greater modularity due to the reduced cardinality. Additionally cardinality of the entire type can he reduced to only analyzing the the final result of the composition.
F = A . B . C
The cardinality of F is all that needs to be known. The cardinality of the individual components can be ignored and you don't have to reason about this. This is not the case for higher order functions.In general business problems that we deal with do not Require higher order functions. Higher order functions share isomorphisms with two concepts we are already familiar with: frameworks and metaprogramming.
Frameworks are programs that call your code and metaprogramming is code that writes and calls code. These things are no different then a function that takes another function in as a parameter and calls it. All three concepts are aspects of the same thing.
Therefore all the downsides of metaprogramming and all the downsides of frameworks are applicable to higher order functions.
Most of the programming problems we solve in the real world do not require metaprogramming. It can easily be avoided and solved with much more composition of combinators. Again, not saying to avoid the usage completely but the usage of higher order functions like metaprogramming should be minimal and used sparingly.
I cannot agree here, being currently involved in modeling and simulation work that would heavily benefit from more advanced type systems. A user would usually prefer to interact with a system with basic elements, but the interactions of the underlying domain concepts really do want to be captured with a richer set of tools.
And, to head off the "in general", there is a long tail of unique problem domains. Each individually doesn't get as much press as, say, a CRUD app, but that doesn't mean that simpler problem domains are necessarily in the majority.
As a silly made up example, you pass a callback to an XML parser to tell you when a node has been parsed, so you can pass in a callback to call an API to let it know. Doing something like that just pollutes the XML parser code for no good reason, and you then debug and find you get a 400 error when trying to parse something and 'wtf' and you have to untangle the callbacks.
I'm having a hard time summing it up right now, but I think if you try to "libraritize" "specific code", i.e. make something that does a very specific job, too generic it causes confusion.
That example has 1 callback but believe me some developers want to stack 'em high.
Although your opinion is more extreme than I can get behind, I don't entirely disagree. I find monads and applicative functors are far easier to understand when presented in their non-higher-order formulation -- that is, "flatten" instead of "bind", and "merge" instead of "ap". You still need to understand "map", but that's an incredibly common pattern that you can get a lot out of for putting in just a little.
I wrote a little comment on this earlier this week.
https://news.ycombinator.com/item?id=24894849
I also agree with your opinion elsewhere about preferring combinators instead of directly passing higher-order functions, but I suspect that's much more of a style and design issue than a language feature distinction. You still need higher-order functions (and I'm frustrated weekly by Java's lack of higher-kinded types or pleasant existential types) as the basement layer to expose a beautiful API in more natural terms.
Use sparingly. I would avoid altogether except for common ones like map, reduce and filter.
Functional programming promotes the idea of composition of morphisms and point free programming. I would use this in place of Object composition/dependency injection/higher order functions or whatever you want to call it.