├── .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 |
2 | title: ES Function.prototype.memo (2021)
3 | status: proposal
4 | stage: 0
5 | location: https://github.com/js-choi/proposal-function-memo
6 | copyright: false
7 | contributors: J. S. Choi
8 |