{"id":18137820,"url":"https://github.com/tc39/proposal-function-memo","last_synced_at":"2025-04-19T17:43:38.596Z","repository":{"id":44353373,"uuid":"475576538","full_name":"tc39/proposal-function-memo","owner":"tc39","description":"A TC39 proposal for function memoization in the JavaScript language.","archived":false,"fork":false,"pushed_at":"2022-07-22T13:31:42.000Z","size":35,"stargazers_count":79,"open_issues_count":12,"forks_count":2,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-03-29T11:03:07.866Z","etag":null,"topics":["proposal","tc39"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tc39.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-03-29T18:47:13.000Z","updated_at":"2025-01-05T01:59:15.000Z","dependencies_parsed_at":"2022-07-30T23:37:56.446Z","dependency_job_id":null,"html_url":"https://github.com/tc39/proposal-function-memo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-function-memo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-function-memo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-function-memo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-function-memo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tc39","download_url":"https://codeload.github.com/tc39/proposal-function-memo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249751720,"owners_count":21320358,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["proposal","tc39"],"created_at":"2024-11-01T15:07:04.785Z","updated_at":"2025-04-19T17:43:38.577Z","avatar_url":"https://github.com/tc39.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Function.prototype.memo for JavaScript\nECMAScript Stage-1 Proposal.\n\nChampions: Hemanth HM; J. S. Choi.\n\n## Rationale\n[Function memoization][] is a common technique that caches the results of\nfunction calls and returns the cached results when the same inputs occur again.\nThese are useful for:\n\n* Optimizing expensive function calls (e.g., factorials, Fibonacci numbers) in a space–time tradeoff.\n* Caching state→UI calculations (e.g., in [React’s useMemo][]).\n* Ensuring that callbacks always return the same [singleton object][].\n* [Mutually recursive][] [recursive-descent parsing][].\n* Implementing [hashlife from cellular automata][hashlife].\n* [Materializing views for database queries][materialized views].\n* [Tabling in logic programming][logic tabling].\n\n[function memoization]: https://en.wikipedia.org/wiki/Memoization\n[React’s useMemo]: https://reactjs.org/docs/hooks-reference.html#usememo\n[singleton object]: https://en.wikipedia.org/wiki/Singleton_pattern\n[mutually recursive]: https://en.wikipedia.org/wiki/Mutual_recursion\n[recursive-descent parsing]: https://en.wikipedia.org/wiki/Recursive_descent_parser\n[hashlife]: https://en.wikipedia.org/wiki/Hashlife\n[materialized views]: https://en.wikipedia.org/wiki/Materialized_view\n[logic tabling]: https://www.metalevel.at/prolog/memoization\n\nMemoization is useful, common, but annoying to write.\nWe propose exploring the addition of a memoization API to the JavaScript language.\n\nIf this proposal is approved for Stage 1, then we would explore various\ndirections for the API’s design. We would also assemble as many real-world use\ncases as possible and shape our design to fulfill them.\n\nIn addition, if both [proposal-policy-map-set][] and this proposal are approved for\nStage 1, then we would explore how memoized functions could use these data\nstructures to control their caches’ memory usage.\n\n[proposal-policy-map-set]: https://github.com/js-choi/proposal-policy-map-set\n\n## Description\nThe Function.prototype.memo method would create a new function that calls the\noriginal function at most once for each tuple of given arguments. Any\nsubsequent calls to the new function with identical arguments would return the\nresult of the first call with those arguments.\n\n```js\nfunction f (x) { console.log(x); return x * 2; }\n\nconst fMemo = f.memo();\nfMemo(3); // Prints 3 and returns 6.\nfMemo(3); // Does not print anything. Returns 6.\nfMemo(2); // Prints 2 and returns 4.\nfMemo(2); // Does not print anything. Returns 4.\nfMemo(3); // Does not print anything. Returns 6.\n```\n\nAdditionally, we may also add a function-decorator version: `@Function.memo`.\nThis would make it easier to apply memoization to function declarations:\n\n```js\n@Function.memo\nfunction f (x) { console.log(x); return x * 2; }\n```\n\nEither version would work with recursive functions:\n\n```js\n// Version with prototype method:\nconst getFibonacci = (function (n) {\n  if (n \u003c 2) {\n    return n;\n  } else {\n    return getFibonacci(n - 1) +\n      getFibonacci(n - 2);\n  }\n}).memo();\nconsole.log(getFibonacci(100));\n\n// Version with function decorator:\n@Function.memo\nfunction getFibonacci (n) {\n  if (n \u003c 2) {\n    return n;\n  } else {\n    return getFibonacci(n - 1) +\n      getFibonacci(n - 2);\n  }\n}\nconsole.log(getFibonacci(100));\n```\n\n### Result caches\nThe developer would be able to pass an optional `cache` argument. This argument\nmust be a Map-like object with `.has`, `.get`, and `.set` methods. In\nparticular, there is a [proposal for Map-like objects with cache-replacement\npolicies like LRUMap][proposal-policy-map-set], which would allow developers to\neasily specify that the memoized function use a memory-constrained cache.\n\n[proposal-policy-map-set]: https://github.com/js-choi/proposal-policy-map-set\n\nThere are at least two possible ways we could design the `cache` parameter; see\n[Issue 3][] and [Issue 4][].\n\n#### Tuple keys?\nA: We could use [tuples][] as the cache’s keys. Each tuple represents a\nfunction call to the memoized function, and the tuple would be of the form `#[thisVal, newTargetVal, ...args]`.\n\nObject values would be replaced by symbols that uniquely identify that object.\n(Tuples cannot directly contain objects. The memoized function’s closure would\nclose over an internal WeakMap that maps objects to their symbols.)\n\n```js\nconst cache = new LRUMap(256);\nconst f = (function f (arg0) { return this.x + arg0; }).memo(cache);\nconst o0 = { x: 'a' }, o1 = { x: 'b' };\nf.call(o0, 0); // Returns 'a0'.\nf.call(o1, 1); // Returns 'b1'.\n```\n\nNow cache would be `LRUMap(2) { #[s0, undefined, 0] ⇒ 'a0', #[s1, undefined, 1]\n⇒ 'b1' }`, where `s0` and `s1` are unique symbols. `f`’s closure would\ninternally close over a `WeakMap { o0 ⇒ s0, o1 ⇒ s1 }`.\n\nThe default behavior of `memo` (i.e., without giving a `cache` argument) is uncertain (see [Issue 3][]). It probably would be simply be an unbounded ordinary Map. (WeakMaps cannot contain tuples as their keys.)\n\n[tuples]: https://github.com/tc39/proposal-record-tuple\n\n#### Composite keys?\nB: Another choice for cache’s keys is [composite keys][]. Each composite key\nrepresents a function call to the memoized function, and the composite key would be of the form `compositeKey(thisVal, newTargetVal, ...args)`.\n\n```js\nconst cache = new LRUMap(256);\nconst f = (function f (arg0) { return this.x + arg0; }).memo(cache);\nconst o0 = { x: 'a' }, o1 = { x: 'b' };\nf.call(o0, 0); // Returns 'a0'.\nf.call(o1, 1); // Returns 'b1'.\n```\n\nNow cache would be `LRUMap(2) { compositeKey(o0, undefined, 0) ⇒ 'a0',\ncompositeKey(o1, undefined, 1) ⇒ 'b1' }`.\n\nThe default behavior of `memo` (i.e., without giving a `cache` argument) is\nuncertain (see [Issue 3][]). It probably would simply be a WeakMap,\nwhich would be able to contain composite keys as their keys.\n\n[composite keys]: https://github.com/tc39/proposal-richer-keys/tree/master/compositeKey\n\n## Unresolved questions\n\n### [Issue 2][]:\nShould `memo` be a prototype method, a static function, a function decorator,\nor multiple things?\n\n### [Issue 3][]:\nHow should cache garbage collection work? (Using WeakMaps for the caches would\nbe ideal…except that WeakMaps do not support primitives as keys.)\n\nShould we just use Maps and make the developer manage the cache memory\nthemselves? (See also [LRUMap and LFUMap][].)\n\nThere is also the [compositeKeys proposal][].\n\n[LRUMap and LFUMap]: https://github.com/js-choi/proposal-policy-map-set\n[compositeKeys proposal]: (https://github.com/tc39/proposal-richer-keys/tree/master/compositeKey)\n\n### [Issue 4][]:\nIf we go with a Map cache, how should we structure the cache? For example, we\ncould use a tree of Maps, or we could use argument-[tuples][] as keys in one\nMap.\n\n[tuples]: https://github.com/tc39/proposal-record-tuple\n\n### [Issue 5][]:\nHow should function calls be considered “equivalent”? How are values compared\n(e.g., with SameValue like `===` or SameValueZero like `Map.get`)? Are the\n`this`-binding receiver and the `new.target` value also used in comparison?\n\n## Precedents\n\n* [lodash.memoize](https://lodash.com/docs/4.17.15#memoize)\n* [Undescore.js memoize](https://underscorejs.org/#memoize)\n* [Python functools.lru_cache][]\n* [Wikipedia “function memoization” article][function memoization]\n\n[Python functools.lru_cache]: https://docs.python.org/3/library/functools.html#functools.lru_cache\n\n[Issue 2]: https://github.com/js-choi/proposal-function-memo/issues/2\n[Issue 3]: https://github.com/js-choi/proposal-function-memo/issues/3\n[Issue 4]: https://github.com/js-choi/proposal-function-memo/issues/4\n[Issue 5]: https://github.com/js-choi/proposal-function-memo/issues/5\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftc39%2Fproposal-function-memo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftc39%2Fproposal-function-memo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftc39%2Fproposal-function-memo/lists"}