Hacker News new | ask | show | jobs
by nendroid 2051 days ago
I disagree with the higher order functions thing. Writing functions that receive other functions as input can lead to over complicated code that's really hard to read. It's literally the same thing as dependency injection just with functions instead of objects.

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.

3 comments

Writing functions that receive other functions as input can lead to over complicated code that's really hard to read.

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.

If I have a "known" function, map() for instance, and I pass a function into it, that's fine, because I already know what map() does. But if I have a function foo() that takes another function bar(), and I don't know what either one does, then I have to look at both foo() and bar() to understand what is going on. I have to look at two places, not just one.

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.

So you like writing functions that take 3 functions as input parameters and returns a new function that also takes a function as an input parameter and returns another function? In general excessive use of this at great depth leads to code that is not only less readable but less modular.

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.

> In general business problems that we deal with do not Require higher order functions.

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.

I refer to business problems but in reality what I said applies to all problems.
I agree to some extent. When I see this in imperative code-bases, it can be used as a way to reduce lines of code and save having to define a new class. But when debugging or trying to make sense of the code it can be awful. However it's not the passing functions to functions that is the problem, it is the division of responsibility not being though through while doing so.

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.

> Writing functions that receive other functions as input can lead to over complicated code that's really hard to read.

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.