Cljs cost of lambda functions

In a library of mine that renders and sorts reagent tables, I made the option of specifying a sort-fn which is a function which, receiving a row, returns the value by which this row should be sorted. In a strange bug that I’ve resolved but am still trying to understand, if the sortfn is any kind of lambda (either (fn) or #()), in a live app with large entries, sort simply fails on those rows and I get unpredictable behavior. However, if the identical function is made into a defn instead of a lambda, everything runs smoothly. I’m guessing it has something to do with some kind of overhead causing lambdas to be redefined for each row, while defns are perhaps not? For example, it works fine sortfn :keyword but dies on sortfn #(:keyword %). Can you help me understand what underlying mechanisms are at work here?

You haven’t explained what you mean by “dies” or failure. it’s hard to diagnose the problem without knowing all the details or the code.

From what it sounds like, you might be implicitly relying on the component you pass a sortfn into not re-rendering after initial mount. Because you’re defining the function inline, each time the parent re-renders, it will pass a new function object in as sortfn and reagent/React will detect this and trigger a re-render and commit of the component.

This is purely speculation based on the bit of info you gave. A deeper insight might be gained by pasting the code of the component and how it’s used, and/or explaining the failure behavior more.

I don’t think I understand the problem well enough. This is a general function feature/trap.

Functions bindings include the surrounding bindings. In a top-level function, that’s the other top level bindings in the namespace. In a second level function, it will take the bindings of the enclosing form as well. It’s a feature. It can also yield unexpected results.

The general rule is to check the bindings visible to the function. It may be you are using an unexpected binding.

I think it has to do with what has been mentioned about function context. Here’s the library code:

Define a sort-fn for a given column in the initial table-column map:

Execute sort on click (or reverse direction of sort):

Sorry for not including this originally. In any case, it’s not obvious to my eyes why lambdas would fail (only on non-trivial input, apparently; it works in the libraries test code, but in prod my entries may be 1k per entry with nested maps).

You still haven’t explained what you mean by “fail”.

AFAICT this library will completely re-generate the headers and rows on every re-render, and doesn’t use React keys. So the performance will be quite bad on large tables.

Using an inline function is short circuiting reagent’s optimizations to not re-render when props haven’t changed. Which is a problem, but it’s exacerbated by the fact that this library is written in such a way that re-rendering the table is incredibly expensive.

2 Likes

Definition of fail: on a 5-columns table in which two columns have lambda sortfn and the others have defn functions, the sort-fn which works on clicking the keyword columns apparently aborts upon clicking one of the lambda column headers, resulting in a single sort but no update of the header class (queued to happen after the click) and header unresponsive to a second click (which is supposed to reverse the sort). However, the full runtime (or at least, figwheel) has not crashed, because I can subsequently click on one of the keyword-sortfn columns and they behave as expected.

Excellent point about react keys. I’ll need to look more into that (I don’t know exactly how they are used outside causing error spam from reagent).

You may want to post this question on StackOverflow, along with sample data and expected/actual output.

1 Like