proposal-function-memo icon indicating copy to clipboard operation
proposal-function-memo copied to clipboard

Cache parameter: optional or required?

Open js-choi opened this issue 3 years ago • 6 comments

Should the user be required to specify a cache, i.e., what cache-replacement policy they wish to use for memoization?

If the cache parameter is optional, then its default presumably would be an ordinary, unbounded Map.

Feedback from @sffc (and I think from @littledan?) at the plenary today is that requiring explicit cache management here is good: that we should require the user to specify a cache.

(An interesting note: Python initially had an lru_cache—a memoizing function that required the user to specify an LRU/least-recently-used cache-replacement policy—but later added a cache function that was the same, except it used a “default” unbounded cache. In other words, the new cache() is equivalent to lru_cache(maxsize=None).)

js-choi avatar Jul 21 '22 23:07 js-choi

Having an unbounded cache is a major footgun in programming. If we make it too easy for developers to push their problems into an unbounded cache, we start crashing Android apps and web sites that run on memory-constrained devices. This is not a theoretical concern; I have had to fix bugs relating to caches in ICU when we started running out of memory. So if there is a default, it should not be an unbounded default. To avoid confusion, I suggest that we just don't have a default.

sffc avatar Jul 22 '22 00:07 sffc

Regarding the Python note: we should remember that Python and JavaScript are targeted to different devices. JavaScript code runs on web browsers, phones, and IoT devices thanks to XS. Python code runs primarily on servers and personal computers. Therefore, Python has different design constraints and I don't think we should use that as precedence for the purposes of JavaScript's decision on this part of the problem space.

sffc avatar Jul 22 '22 00:07 sffc

I'm going to argue that it should be optional. For example, I often like to employ memoization to help optimize resource-fetching functions that might not even take parameters. For example, "fetchConfigFromDisk()", or, "getInformationFromEndpoint()". It would be annoying to have to specify a cache-replacement policy for common use-cases like this, when the obvious default would suffice just fine.

Update: (Just putting down some more thoughts)

I know @sffc gave evidence otherwise, but I would hope that for other use casese, developers wouldn't be foolish enough to spend the effort to optimize how quickly a function runs, while simultaneously de-optimizing the memory usage of the same code to something unacceptable. (I don't intend "foolish" to be a strong word here, we're all occasionally foolish as we code, myself included). I would hope that, as they place the memoize() function in, they at least consider the memory impact it will have, and that it's not some universal silver bullet you should place anywhere without thought to make things faster.

In the end, I don't feel overly strong about the statement I presented. Yes, my preference would be to have this be an optional parameter, but I really wouldn't mind that much if I had to provide it every time.

theScottyJam avatar Aug 21 '22 20:08 theScottyJam

I'm going to argue that cache should not be controlled by the developer.

Having the cache controlled by the developer could cause memory problems because they do not necessarily have insight into how memory-constrained the runtime environment is. They could pre-allocate memory that is ultimately unused (few function calls or small set of arguments used) or not well-used (mostly cache misses), depriving the browser of memory it could use elsewhere.

The browser, in contrast, has much more insight into how much memory is available and which functions represent "hot paths." (I'm not arguing for automatic memoization because the browser doesn't know which functions are pure and which have side-effects.) The browser could manage cache and even de-memoize (or never actually memoize) functions that are created with Function.memoize(fn) or fn.memo(). The developer is really only indicating (1) the function is pure, (2) it is expected to be called often, and (3) it may be slow to run.

NickGard avatar Sep 29 '22 16:09 NickGard

I think both of our use cases here could be satisfied, they don't need to be at odds with each other. You could have a cache where both the user and the engine are allowed to eject items from it. The user can do so when it knows a cache entry to be stale, and the engine can do so when it realizes it's limited on memory. The engine can also choose to simply not add items to the cache in the first place.

theScottyJam avatar Sep 29 '22 22:09 theScottyJam

95% of the time, devs just want a basic cache where each parameter is === compared and the cached result returned if it matches. It doesn't make sense to me to force devs to always pass in the default cache.

The complex cache config object is almost always going to be used by library makers for other library makers. Forcing it onto users would discourage use.

There's also another option where you have a basic memo function that takes no parameters and an advanced memo function that requires the cache parameter.

elijahrdorman avatar Apr 08 '24 17:04 elijahrdorman