NoEtaReducibleLambdas elm-review rule

Hey @john_s!

Very nice work! I know quite a few people are interested in this because I’ve seen asked a number of times whether this existed. Well now it does! :raised_hands:

You mention changing performance attributes, so I just want to mention that even the changes that this rule makes can have some performance impacts.

When they have multiple arguments, Elm functions are wrapped in a FX function (F2, F3, F4, …), and when functions are called with multiple argument, they are called with a AX function (A2, A3, A4, …). What the AX functions do is check whether the function to call is wrapped in a FX function of the same number (A2 and F2, A3 and F3, …). If it’s the case, then the function is called with the original functions like f(a, b, c, ...). If it isn’t, then the function will be called one argument at a time f(a)(b)(c)... until all the arguments are passed, which is noticeably slower for a function that is often called.

@robin.heggelund wrote a very nice series of articles on Elm performance, including one article explaining How Elm functions work, and its following articles explain how they got faster (links to those at the end of this article).

That means that List.map (\apples bananas -> someFunction milk apples bananas) list will be faster than List.map (someFunction milk) list. Of course, always benchmark instead of making a claim, so I wrote a benchmark.

For Elm 0.19.1, performance is faster with the curried version (I was very surprised to be honest, and I don’t know how to explain) by 5%-10%. However, when run with elm-optimize-level-2, the curried version is 3.5 times slower (according to this particular benchmark at least).

Of course, this rule can also have the opposite effect. when you have a function defined as someFunction a = \b -> ..., then having it called like List.map (someFunction 1) list should be more performant than List.map (\a -> someFunction 1 a) list. In this case, the rule would push towards the former version which would be a good thing.

These performance impacts is the reason why it didn’t explore this rule further. But I think that there are some cases where it’s always going to be okay or better (on a performance level, not in terms of personal preferences), like for \apples -> someFunction apples or \apples bananas -> someFunction apples bananas being reduced to someFunction.

I think I would see the value in having a setting for the rule that only reports errors when the performance can be estimated to be at least as good as the current version. I see at least 2 scenarios when it could do that:

  1. When the entire lambda can be reduced to a single variable name, like \apples -> someFunction applessoimeFunction (that said, this should be benchmarked)
  2. When the rule knows which wrapper the function uses. So if we have someFunction milk = \apples bananas -> ..., then we can simplify \apples bananas -> someFunction milk apples bananas to someFunction milk (and otherwise this should be left untouched). This information can be gathered by looking at the definitions of each function. When the information is missing (code from dependencies, or functions from arguments), then no error should be reported.

What do you think?

PS: What does “Eta” mean?

2 Likes