├── .editorconfig ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HISTORY.md ├── LICENSE.md ├── README.md ├── package.json └── spec.html /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | out 4 | dist 5 | npm-debug.log 6 | deploy_key 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | This repository is a TC39 project, and it therefore subscribes to its [code of 3 | conduct][CoC]. It is available at . 4 | 5 | We all should strive here to be respectful, friendly and patient, inclusive, 6 | considerate, and careful in the words we choose. When we disagree, we should try 7 | to understand why. 8 | 9 | To ask a question or report an issue, please follow the [CoC]’s directions, 10 | e.g., emailing [tc39-conduct-reports@googlegroups.com][]. 11 | 12 | More information about contributing is also available in [CONTRIBUTING.md][]. 13 | 14 | [CoC]: https://tc39.es/code-of-conduct/ 15 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com 16 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this proposal 2 | First off, thank you for taking the time to contribute! 🎉 3 | 4 | Here are some suggestions to contributing to this proposal. 5 | 6 | 1. The general [TC39 Process][], which summarizes 7 | how TC39’s “consensus” and “Stages” work. 8 | 2. The guide on [contributing to TC39 proposals][contributing guide]. 9 | 3. The [TC39 Code of Conduct][CoC]: 10 | It has important information about how we’re all expected to act 11 | and what to do when we feel like someone’s conduct does not meet the Code. 12 | We all want to maintain a friendly, productive working environment! 13 | 4. The [TC39 How to Give Feedback][feedback] article. 14 | 5. The [proposal explainer][] to make sure that it is 15 | not already addressed there. 16 | 6. The [TC39 Matrix guide][] (if you want to chat with TC39 members on Matrix, 17 | which is a real-time chat platform). 18 | 7. If the explainer does not already explain your topic adequately, 19 | then please [search the GitHub repository’s issues][issues] 20 | to see if any issues match the topic you had in mind. 21 | This proposal is more than four years old, 22 | and it is likely that the topic has already been raised and thoroughly discussed. 23 | 24 | You can leave a comment on an [existing GitHub issue][issues], 25 | create a new issue (but do try to [find an existing GitHub issue][issues] first), 26 | or [participate on Matrix][TC39 Matrix guide]. 27 | 28 | Please try to keep any existing GitHub issues on their original topic. 29 | 30 | If you feel that someone’s conduct is not meeting the [TC39 Code of Conduct][CoC], 31 | whether in this GitHub repository or in a [TC39 Matrix room][TC39 Matrix guide], 32 | then please follow the [Code of Conduct][CoC]’s directions for reporting the violation, 33 | including emailing [tc39-conduct-reports@googlegroups.com][]. 34 | 35 | Thank you again for taking the time to contribute! 36 | 37 | [CoC]: https://tc39.es/code-of-conduct/ 38 | [TC39 process]: https://tc39.es/process-document/ 39 | [contributing guide]: https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md#new-feature-proposals 40 | [feedback]: https://github.com/tc39/how-we-work/blob/master/feedback.md 41 | [proposal explainer]: https://github.com/tc39/proposal-array-from-async/blob/main/README.md 42 | [TC39 Matrix guide]: https://github.com/tc39/how-we-work/blob/master/matrix-guide.md 43 | [issues]: https://github.com/tc39/proposal-array-from-async/issues?q=is%3Aissue+ 44 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com 45 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Brief history of ES Function.prototype.memo 2 | For information on what “Stage 1” and “Stage 2” mean, 3 | read about the [TC39 Process][]. 4 | 5 | More information about contributing is also available in [CONTRIBUTING.md][]. 6 | 7 | ## 2015–2021 8 | The pipe champion group presents F# pipes (a tacit-unary-function-application 9 | operator) for Stage 2 twice to TC39, being unsuccessful both times due to 10 | pushback from multiple other TC39 representatives’ memory performance concerns, 11 | syntax concerns about await, and concerns about encouraging ecosystem 12 | bifurcation/forking. 13 | 14 | For more information, see the [pipe proposal’s HISTORY.md][pipe history]. 15 | 16 | ## 2021-09 17 | Inspired by [pipe issue #233][], [@js-choi][] creates a new proposal 18 | that would add several Function helper methods. 19 | 20 | ## 2021-10 21 | [On 2021-10, proposal-function-helpers is presented to the Committee 22 | plenary][2021-10] for Stage 1. The Committee rejects the proposal due to its 23 | being overly broad and requests that it be split up into multiple proposals. 24 | 25 | ## 2022-03 26 | [On 2022-03, a TC39 incubator meeting is held][2022-03 incubator] to discuss 27 | Function helpers in general. Several representatives express support for 28 | Function.prototype.once, and it is decided to pursue Function.prototype.once 29 | for Stage 1. 30 | 31 | [TC39 process]: https://tc39.es/process-document/ 32 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md 33 | 34 | [pipe history]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md 35 | [pipe issue #233]: https://github.com/tc39/proposal-pipeline-operator/issues/233 36 | 37 | [@js-choi]: https://github.com/js-choi 38 | 39 | [2021-10]: https://github.com/tc39-transfer/proposal-function-helpers/issues/17#issuecomment-953814353 40 | 41 | [2022-03 incubator]: https://github.com/tc39/incubator-agendas/blob/main/notes/2022/03-08.md 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 J. S. Choi 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | **This software is provided by the copyright holders and contributors 19 | “as is” and any express or implied warranties, including, but not 20 | limited to, the implied warranties of merchantability and fitness for a 21 | particular purpose are disclaimed. In no event shall the copyright 22 | holder or contributors be liable for any direct, indirect, incidental, 23 | special, exemplary, or consequential damages (including, but not limited 24 | to, procurement of substitute goods or services; loss of use, data, or 25 | profits; or business interruption) however caused and on any theory of 26 | liability, whether in contract, strict liability, or tort (including 27 | negligence or otherwise) arising in any way out of the use of this 28 | software, even if advised of the possibility of such damage.** 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Function.prototype.memo for JavaScript 2 | ECMAScript Stage-1 Proposal. 3 | 4 | Champions: Hemanth HM; J. S. Choi. 5 | 6 | ## Rationale 7 | [Function memoization][] is a common technique that caches the results of 8 | function calls and returns the cached results when the same inputs occur again. 9 | These are useful for: 10 | 11 | * Optimizing expensive function calls (e.g., factorials, Fibonacci numbers) in a space–time tradeoff. 12 | * Caching state→UI calculations (e.g., in [React’s useMemo][]). 13 | * Ensuring that callbacks always return the same [singleton object][]. 14 | * [Mutually recursive][] [recursive-descent parsing][]. 15 | * Implementing [hashlife from cellular automata][hashlife]. 16 | * [Materializing views for database queries][materialized views]. 17 | * [Tabling in logic programming][logic tabling]. 18 | 19 | [function memoization]: https://en.wikipedia.org/wiki/Memoization 20 | [React’s useMemo]: https://reactjs.org/docs/hooks-reference.html#usememo 21 | [singleton object]: https://en.wikipedia.org/wiki/Singleton_pattern 22 | [mutually recursive]: https://en.wikipedia.org/wiki/Mutual_recursion 23 | [recursive-descent parsing]: https://en.wikipedia.org/wiki/Recursive_descent_parser 24 | [hashlife]: https://en.wikipedia.org/wiki/Hashlife 25 | [materialized views]: https://en.wikipedia.org/wiki/Materialized_view 26 | [logic tabling]: https://www.metalevel.at/prolog/memoization 27 | 28 | Memoization is useful, common, but annoying to write. 29 | We propose exploring the addition of a memoization API to the JavaScript language. 30 | 31 | If this proposal is approved for Stage 1, then we would explore various 32 | directions for the API’s design. We would also assemble as many real-world use 33 | cases as possible and shape our design to fulfill them. 34 | 35 | In addition, if both [proposal-policy-map-set][] and this proposal are approved for 36 | Stage 1, then we would explore how memoized functions could use these data 37 | structures to control their caches’ memory usage. 38 | 39 | [proposal-policy-map-set]: https://github.com/js-choi/proposal-policy-map-set 40 | 41 | ## Description 42 | The Function.prototype.memo method would create a new function that calls the 43 | original function at most once for each tuple of given arguments. Any 44 | subsequent calls to the new function with identical arguments would return the 45 | result of the first call with those arguments. 46 | 47 | ```js 48 | function f (x) { console.log(x); return x * 2; } 49 | 50 | const fMemo = f.memo(); 51 | fMemo(3); // Prints 3 and returns 6. 52 | fMemo(3); // Does not print anything. Returns 6. 53 | fMemo(2); // Prints 2 and returns 4. 54 | fMemo(2); // Does not print anything. Returns 4. 55 | fMemo(3); // Does not print anything. Returns 6. 56 | ``` 57 | 58 | Additionally, we may also add a function-decorator version: `@Function.memo`. 59 | This would make it easier to apply memoization to function declarations: 60 | 61 | ```js 62 | @Function.memo 63 | function f (x) { console.log(x); return x * 2; } 64 | ``` 65 | 66 | Either version would work with recursive functions: 67 | 68 | ```js 69 | // Version with prototype method: 70 | const getFibonacci = (function (n) { 71 | if (n < 2) { 72 | return n; 73 | } else { 74 | return getFibonacci(n - 1) + 75 | getFibonacci(n - 2); 76 | } 77 | }).memo(); 78 | console.log(getFibonacci(100)); 79 | 80 | // Version with function decorator: 81 | @Function.memo 82 | function getFibonacci (n) { 83 | if (n < 2) { 84 | return n; 85 | } else { 86 | return getFibonacci(n - 1) + 87 | getFibonacci(n - 2); 88 | } 89 | } 90 | console.log(getFibonacci(100)); 91 | ``` 92 | 93 | ### Result caches 94 | The developer would be able to pass an optional `cache` argument. This argument 95 | must be a Map-like object with `.has`, `.get`, and `.set` methods. In 96 | particular, there is a [proposal for Map-like objects with cache-replacement 97 | policies like LRUMap][proposal-policy-map-set], which would allow developers to 98 | easily specify that the memoized function use a memory-constrained cache. 99 | 100 | [proposal-policy-map-set]: https://github.com/js-choi/proposal-policy-map-set 101 | 102 | There are at least two possible ways we could design the `cache` parameter; see 103 | [Issue 3][] and [Issue 4][]. 104 | 105 | #### Tuple keys? 106 | A: We could use [tuples][] as the cache’s keys. Each tuple represents a 107 | function call to the memoized function, and the tuple would be of the form `#[thisVal, newTargetVal, ...args]`. 108 | 109 | Object values would be replaced by symbols that uniquely identify that object. 110 | (Tuples cannot directly contain objects. The memoized function’s closure would 111 | close over an internal WeakMap that maps objects to their symbols.) 112 | 113 | ```js 114 | const cache = new LRUMap(256); 115 | const f = (function f (arg0) { return this.x + arg0; }).memo(cache); 116 | const o0 = { x: 'a' }, o1 = { x: 'b' }; 117 | f.call(o0, 0); // Returns 'a0'. 118 | f.call(o1, 1); // Returns 'b1'. 119 | ``` 120 | 121 | Now cache would be `LRUMap(2) { #[s0, undefined, 0] ⇒ 'a0', #[s1, undefined, 1] 122 | ⇒ 'b1' }`, where `s0` and `s1` are unique symbols. `f`’s closure would 123 | internally close over a `WeakMap { o0 ⇒ s0, o1 ⇒ s1 }`. 124 | 125 | The 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.) 126 | 127 | [tuples]: https://github.com/tc39/proposal-record-tuple 128 | 129 | #### Composite keys? 130 | B: Another choice for cache’s keys is [composite keys][]. Each composite key 131 | represents a function call to the memoized function, and the composite key would be of the form `compositeKey(thisVal, newTargetVal, ...args)`. 132 | 133 | ```js 134 | const cache = new LRUMap(256); 135 | const f = (function f (arg0) { return this.x + arg0; }).memo(cache); 136 | const o0 = { x: 'a' }, o1 = { x: 'b' }; 137 | f.call(o0, 0); // Returns 'a0'. 138 | f.call(o1, 1); // Returns 'b1'. 139 | ``` 140 | 141 | Now cache would be `LRUMap(2) { compositeKey(o0, undefined, 0) ⇒ 'a0', 142 | compositeKey(o1, undefined, 1) ⇒ 'b1' }`. 143 | 144 | The default behavior of `memo` (i.e., without giving a `cache` argument) is 145 | uncertain (see [Issue 3][]). It probably would simply be a WeakMap, 146 | which would be able to contain composite keys as their keys. 147 | 148 | [composite keys]: https://github.com/tc39/proposal-richer-keys/tree/master/compositeKey 149 | 150 | ## Unresolved questions 151 | 152 | ### [Issue 2][]: 153 | Should `memo` be a prototype method, a static function, a function decorator, 154 | or multiple things? 155 | 156 | ### [Issue 3][]: 157 | How should cache garbage collection work? (Using WeakMaps for the caches would 158 | be ideal…except that WeakMaps do not support primitives as keys.) 159 | 160 | Should we just use Maps and make the developer manage the cache memory 161 | themselves? (See also [LRUMap and LFUMap][].) 162 | 163 | There is also the [compositeKeys proposal][]. 164 | 165 | [LRUMap and LFUMap]: https://github.com/js-choi/proposal-policy-map-set 166 | [compositeKeys proposal]: (https://github.com/tc39/proposal-richer-keys/tree/master/compositeKey) 167 | 168 | ### [Issue 4][]: 169 | If we go with a Map cache, how should we structure the cache? For example, we 170 | could use a tree of Maps, or we could use argument-[tuples][] as keys in one 171 | Map. 172 | 173 | [tuples]: https://github.com/tc39/proposal-record-tuple 174 | 175 | ### [Issue 5][]: 176 | How should function calls be considered “equivalent”? How are values compared 177 | (e.g., with SameValue like `===` or SameValueZero like `Map.get`)? Are the 178 | `this`-binding receiver and the `new.target` value also used in comparison? 179 | 180 | ## Precedents 181 | 182 | * [lodash.memoize](https://lodash.com/docs/4.17.15#memoize) 183 | * [Undescore.js memoize](https://underscorejs.org/#memoize) 184 | * [Python functools.lru_cache][] 185 | * [Wikipedia “function memoization” article][function memoization] 186 | 187 | [Python functools.lru_cache]: https://docs.python.org/3/library/functools.html#functools.lru_cache 188 | 189 | [Issue 2]: https://github.com/js-choi/proposal-function-memo/issues/2 190 | [Issue 3]: https://github.com/js-choi/proposal-function-memo/issues/3 191 | [Issue 4]: https://github.com/js-choi/proposal-function-memo/issues/4 192 | [Issue 5]: https://github.com/js-choi/proposal-function-memo/issues/5 193 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proposal-function-memo", 3 | "private": true, 4 | "description": "A TC39 proposal for function memoization in the JavaScript language.", 5 | "author": "J. S. Choi (https://jschoi.org/)", 6 | "license": "BSD-3-Clause", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/tc39/proposal-array-async-from.git" 10 | }, 11 | "keywords": [ 12 | "proposal", 13 | "tc39", 14 | "function", 15 | "callback" 16 | ], 17 | "scripts": { 18 | "prebuild": "mkdir -p dist", 19 | "build": "ecmarkup --verbose spec.html dist/index.html --css dist/ecmarkup.css --js dist/ecmarkup.js", 20 | "watch": "npm run build -- --watch" 21 | }, 22 | "devDependencies": { 23 | "ecmarkup": "^10.0.2" 24 | }, 25 | "homepage": "https://github.com/js-choi/proposal-function-memo" 26 | } 27 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 |

Introduction

14 |
15 | --------------------------------------------------------------------------------