1. 13
  1.  

  2. 17

    I don’t see how this explains why higher-order functions are like inheritance, nor really gives a reason why the example code is bad except being written in a hard-to-follow way.

    1. 3

      Fair enough. I re-read text under code example and it really felt like preaching rather than discussing. I tried to write some more thoughts in there, care to comment on that?

      1. 3

        Now I understand what you mean. I’m not sure if I agree with it at the moment, but it makes clear what you’re saying.

    2. 14

      The Clojure code example is uncompelling. It would be badly written whether or not it used higher-order functions: first and foremost, the meaningless and abbreviated variable and function names make it hard to reason about.

      One side of higher-order functions (either HOF itself or the function that is being passed) should belong to a library. Using HOFs amidst business logic needlessly complicates the code.

      I would be more receptive to the argument if you were able to frame this in terms of trade-offs vs. this kind of all-or-nothing perspective. There are plenty of places that higher-order functions can be confusing and hard to understand, but based on the examples and justification presented here I’m unconvinced that I should now take some arbitrary approach of delegating HOFs or their args to libraries, or whatever.

      More generally I find this kind of dogmatic thinking in the software industry very frustrating. I feel like it’s prevalent especially in the Clojure community, and is absolutely antithetical to what a programming language community should be–especially one that started off by taking all kinds of inspiration from academic compsci and promoting a culture of curiosity and exploration. This is the opposite of that.

      It’s a useful and a powerful tool, but it should be used with restraint and understanding.

      I agree! I wish this piece actually explained what that entails.

      1. 3

        More generally I find this kind of dogmatic thinking in the software industry very frustrating. I feel like it’s prevalent especially in the Clojure community, and is absolutely antithetical to what a programming language community should be–especially one that started off by taking all kinds of inspiration from academic compsci and promoting a culture of curiosity and exploration. This is the opposite of that.

        Very well said sir.

        1. 1

          Well, the tradeoff is obvious: you gain ability to pass strategy down the stack in exchange of simplicity. In my mind it’s a wrong way to do it and usually there are better ways. I guess I’ll need to expand on that.

          the meaningless and abbreviated variable and function names make it hard to reason about.

          Well, I tried to reduce code to something where it’s small enough to not delve into arguing about it being weird in other ways, I guess that’s what I get in result. :)

        2. 10

          You might be interested in defunctionalization, which is another perspective on eliminating HoFs.

          1. 9

            TBH, any code with such terrible names is going to be hard to understand. Is “es-filter-q” Clojure lingo for something?

            And I don’t really agree with the premise that inheritance makes code hard to read. If it’s making the code harder to read it was probably a bad place to use inheritance. Not every tool has to be used in every situation.

            1. 2

              Is “es-filter-q” Clojure lingo for something?

              It makes a part of query (q) to ElasticSearch (es) to generate filter aggregations for our frontend.

              1. 7

                Okay, so rewriting with better names, I don’t think the code is that hard to read:

                (defn create-elastic-search-filter-query [predicate value]
                  {:agg (predicate value)})
                
                (defn by-x [x] {:x x})
                (defn by-y [y] {:y y})
                
                (defn -main []
                  [(create-elastic-search-filter-query by-x :wow)
                   (create-elastic-search-filter-query by-y :naw)])
                
            2. 8

              I think HOF is more like dependency injection (in the sense of passing an interface instance into a ctor in an OOP language) than inheritance.

              They definitely require the reader to jump around to see what’s actually going to execute.

              1. 2

                This is a very insightful observation.

              2. 6

                I don’t agree with the premise that “When a HOF is used in a business logic among other functions, it’s a bad thing”. As this post states, higher-order functions are a pretty simple idea, and that means they are used pervasively in any language that supports them (which is nearly every commonly-used programming language - even C lets you pass a function pointer as an argument to a function, and then call that function. You can write map in C). And of course in functional programing languages, you create and use application-specific higher-order functions all the time, pervasively in a codebase. The idea that you shouldn’t use higher-order functions in business logic is extremely arbitrary to me - it seems like saying that you shouldn’t use, say, early returns in business logic.

                1. 5

                  I see another way that they are the same. When I talk about inheritance, I mention that a good way of thinking about it is that abstract classes are classes with holes in them and subclasses fill the holes. Don’t inherit unless you are filling holes, i.e, overriding abstract methods.

                  HOFs are like those overrides and the places that accept them are the holes.

                  It’s parameterization of behavior and, like the article says, if it’s done excessively it can be confusing.

                  1. 4

                    Well, I guess if using higher-order function feels like programming with callbacks, it’s a smell.

                    1. 4

                      Using HOFs amidst business logic needlessly complicates the code. It’s exactly like inheritance, makes your code hard to follow, hard to reason about, hard to debug, hard to experiment with.

                      That’s the only mention of the word inheritance in the body of the text. It’s also characterized as “extraneous openness and indirection”. Generally, these are just negative qualities that might be ascribed to inheritance, not the concept itself.

                      I happen to agree inheritance is a bit of a misfeature, but the reasoning should be clear and specific. This article seems mostly to say “HOFs can be used in a confusing way”.

                      I agree with that, too. HOFs operate at a higher level of abstraction than 1st order functions. It can be harder to reason at higher orders. This is one of the more direct places where types are really quite useful.

                      But this isn’t an argument that HOFs are like inheritance. It’s not even an argument that either inheritance or HOFs are necessarily causal of “hard to follow, hard to reason about, hard to debug, hard to experiment with”. The only place any assertion like that shows up is in a single specific example and one generalization

                      usually code ends up structured in a way where [the HOF] makes a lot of setup before calling [its argument function], and recreating all that setup takes effort

                      So all this criticism aside, I think there’s some truth to the assertion. And there’s real pain in inheritance and HOFs both being capable of causing difficulty. It’s even, as far as I’m concerned, true that you can model inheritance by taking classes as defining factory functions and inheritance as having those factory functions have access to super-class factory functions: inheritance is a HOF!

                      But there’s a lot going on behind the scenes to get to this point.

                      1. 4

                        I think this is bad because the Clojure code is unreadable. If you use HOFs in restrained, sensible ways, with effective naming conventions, they can be just as clear as everything else:

                        # Python
                        filter(Lease.expiring, leases)
                        
                        # Ruby
                        bills.filter(&:paid?)
                        
                        # Haskell
                        filter (\v -> fuelLevel v > threshold) vehiclesOnLot
                        

                        It all just boils down to careful, reasonable naming.

                        Of course, fold is a little less familiar, so those tend to look “weirder” overall than filter, and unfold is almost always a poor choice just because of its unfamiliarity.

                        1. 2

                          This is funny, because beginning of the post argues that using HOFs in filter is actually okay!

                          1. 1

                            The example you give of bad HOFs is also an application of filter, though.

                        2. 3

                          I love people writing, especially technical essays, so I’m going to upvote this and I appreciate the effort.

                          Having said that, dang if I can come up with anything positive to say here. There’s a lot of theory here that I’m hoping to dodge in my comment, but in general HoF are supposed to simplify your code, not make it harder to understand. These examples seemed to do the opposite. ’ Higher-Order Functions are sorta-kinda like inheritance, and they’re sorta-kinda like callbacks, and they’re sorta-kinda like templates. But they’re not. They’re Higher-Order Functions.

                          I understand that many may find my comment lacking. I’ll try to take some time in the next week or two and explain at length. At the root of this is the problem many coders have with Pure FP: taking parts out and seeing how they would fit into their usual way of coding. I think you always end up with a half-man, half-monster when you do this. It’s not wrong, really, but it’s not entirely useful either.

                          Thanks for the thinking opportunity this morning!

                          1. 3

                            To generalize, abstraction is not free. As soon as you create a new named something in your code (a function, a class, a macro) abstracting away some logic it imposes a cost of reading, understanding and getting used to this abstraction for a reader of the code. This cost is warranted if the abstraction is useful and will be used many times in the future, reducing overall complexity by hiding some implementation details. This is why it works in reusable libraries but not for one-off instances that only make sense in a limited context.

                            It’s not specific to HOFs or inheritance, really.

                            1. 2

                              I can see why you compare that with inheritance only because it’s hard to follow the execution flow, but I find the title misleading in that it makes the reader think it serves the same purpose. To me, this has more to do with the readability problem of callbacks or Continuation Passing Style, and since it has not yet been discussed (except with a comment talking about defunctionalization), I thought it would be good to add this comment.

                              1. 2

                                Interesting! I’d phase this as “hof is an equivalent to template method pattern”.

                                1. 2

                                  Yeah, I did not meant to say it’s equivalent to inheritance. It’s like inheritance in a way that it complicates reading code, convolutes it - you have to jump around to see what’s going on.

                                2. 1

                                  I can see why the example code is bad, and I can see there’s a resemblance between that kind of decorator function and HOFs, but whilst similar they aren’t the same thing, and the criticism of the decorator function doesn’t apply to HOFs.