ITADN

Equality for closure functions

#21Openpzuraq 创建于 2025-08-24
P
pzuraqcommented
One common pattern in JavaScript is to pass a closure as configuration to a function, as in hooks-like functional APIs: ```ts function useApiResponse() { return useQuery({ async queryFn() { const response = await fetch('www.example.com/api'); return response.json(); } }); } ``` Currently, there is no way to easily memoize such an API because each time this code executes, `queryFn` creates a new function. While we could potentially use `toString()` for this, performance is questionable (arbitrarily long, and unknown if engines optimize for that path), and it may not work with [function implementation hiding](https://github.com/tc39/proposal-function-implementation-hiding) if that proposal ever gets merged. It also does not work when the closure function actually closes over some in-scope variable: ```ts function useApiResponse(url) { return useQuery({ async queryFn() { const response = await fetch(url); return response.json(); } }); } ``` Let's assume that the API response always return the same value for a given URL for now. If that is the case, then in theory, the result of `useQuery` should be the same based on the URL it receives: ``` useApiResponse('www.example.come/api'); useApiResponse('www.example.come/api'); // same result as first time useApiResponse('www.other.come/api'); // different result ``` Given this, we conceptually _should_ be able to memoize `useQuery` based on: 1. The function definition (e.g. the string of code that each closure instantiates based on) 2. The scopes that the closure captures (same scopes === same closed over vars === same result when called with same args) For libraries that are exploring more intuitive and performant functional APIs, it would be really useful to be able to have a key based on these two properties. Currently, in my library [Signalium](https://github.com/pzuraq/signalium), I'm needing to use a Babel transform that wraps callbacks: ```ts const useApiResponse = reactive((url) => { return useQuery({ queryFn: callback( async () => { const response = await fetch(url); return response.json(); }, 0, // address of closure in the function [url], // in scope variables } }); }); ``` This API is notably similar to `useCallback` from React, with the only difference being the second parameter, the closure "address". This is basically a way to find the same closure in a given context, with the alternative being we just have an implicit counter in scope like React does, but then we have to always call things in the same order. I mention Signalium here only as a non-React example use-case, because I want to show that this functionality would be useful outside of one programming model. My feeling, based on my experience building Signalium, is that it would be useful in many cases where we want to memoize values that are somehow tied to arguments that are called frequently, and that closures are often used as arguments. So, with all that context, my questions: 1. Has function equality been in general considered, outside of the obvious `===` equality that already is possible (but limited)? 2. Could this type of functionality be in scope for Composites? Would it make more sense as a separate proposal (e.g. something like `Function.getUniqueKey(closure)`?) 3. Is this something that would make sense in the language at all? I definitely wouldn't want to expose the actual scopes of closures, that would be far too powerful, but being able to basically do a quick deep-equal to get a key _seems_ to me like it would be _OK_. I can't think of any way this could be abused currently, but I would love to see if anyone else sees an obvious flaw or issue it could introduce.
20 条评论