memo_wise 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +11 -1
- data/Gemfile.lock +28 -28
- data/README.md +41 -21
- data/benchmarks/Gemfile +2 -2
- data/lib/memo_wise/internal_api.rb +48 -71
- data/lib/memo_wise/version.rb +1 -1
- data/lib/memo_wise.rb +113 -42
- data/memo_wise.gemspec +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b26ef1a44fd13498359282c6d47923ecf90bdfd4e8ac487d389dd1d5c9c5d59
|
4
|
+
data.tar.gz: 51d2ddc7b1d0e7d770afcf081f566059e67b07867c10803db693fd2ec848f4d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca1c06be2c7d0368e7708349563a0a79f93922d491ffe43448793bd6364938ea063bc3910d67ca7ce04c6cb409d784d23d19d5e97fea6430fcb20ebbb0819ab3
|
7
|
+
data.tar.gz: 93fc8811a80bd7f4d88517e0c3cd21f3b6ef29daea6e0578e734abecdc0067f5f9451261f5c47ad80106332075a4072a4a3e7322fe6181f2d61782d63c42caa0
|
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -9,8 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
9
9
|
|
10
10
|
- Nothing yet!
|
11
11
|
|
12
|
+
## [1.4.0] - 2021-12-10
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
|
16
|
+
- Fix several bugs related to classes inheriting memoized methods
|
17
|
+
from multiple modules or a parent class ([#241](https://github.com/panorama-ed/memo_wise/pull/241))
|
18
|
+
|
12
19
|
## [1.3.0] - 2021-11-22
|
13
20
|
|
21
|
+
### Fixed
|
22
|
+
|
14
23
|
- Fix thread-safety issue in concurrent calls to zero-arg method in unmemoized
|
15
24
|
state which resulted in a `nil` value being accidentally returned in one thread
|
16
25
|
- Fix bugs related to child classes inheriting from parent classes that use
|
@@ -101,7 +110,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
101
110
|
- Panolint
|
102
111
|
- Dependabot setup
|
103
112
|
|
104
|
-
[Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1.
|
113
|
+
[Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1.4.0...HEAD
|
114
|
+
[1.4.0]: https://github.com/panorama-ed/memo_wise/compare/v1.3.0...v1.4.0
|
105
115
|
[1.3.0]: https://github.com/panorama-ed/memo_wise/compare/v1.2.0...v1.3.0
|
106
116
|
[1.2.0]: https://github.com/panorama-ed/memo_wise/compare/v1.1.0...v1.2.0
|
107
117
|
[1.1.0]: https://github.com/panorama-ed/memo_wise/compare/v1.0.0...v1.1.0
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
GIT
|
2
2
|
remote: https://github.com/panorama-ed/panolint.git
|
3
|
-
revision:
|
3
|
+
revision: 5850a218b96da7dd196438fb65d41169cd6747e5
|
4
4
|
branch: main
|
5
5
|
specs:
|
6
|
-
panolint (0.1.
|
6
|
+
panolint (0.1.4)
|
7
7
|
brakeman (>= 4.8, < 6.0)
|
8
8
|
rubocop (>= 0.83, < 2.0)
|
9
9
|
rubocop-performance (~> 1.5)
|
@@ -14,19 +14,20 @@ GIT
|
|
14
14
|
PATH
|
15
15
|
remote: .
|
16
16
|
specs:
|
17
|
-
memo_wise (1.
|
17
|
+
memo_wise (1.4.0)
|
18
18
|
|
19
19
|
GEM
|
20
20
|
remote: https://rubygems.org/
|
21
21
|
specs:
|
22
|
-
activesupport (
|
22
|
+
activesupport (6.1.4.1)
|
23
23
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
24
|
-
i18n (>=
|
25
|
-
minitest (
|
26
|
-
tzinfo (~>
|
24
|
+
i18n (>= 1.6, < 2)
|
25
|
+
minitest (>= 5.1)
|
26
|
+
tzinfo (~> 2.0)
|
27
|
+
zeitwerk (~> 2.3)
|
27
28
|
ansi (1.5.0)
|
28
29
|
ast (2.4.2)
|
29
|
-
brakeman (5.1.
|
30
|
+
brakeman (5.1.2)
|
30
31
|
codecov (0.6.0)
|
31
32
|
simplecov (>= 0.15, < 0.22)
|
32
33
|
concurrent-ruby (1.1.9)
|
@@ -36,11 +37,11 @@ GEM
|
|
36
37
|
ansi
|
37
38
|
rouge
|
38
39
|
slop (~> 3)
|
39
|
-
i18n (1.8.
|
40
|
+
i18n (1.8.11)
|
40
41
|
concurrent-ruby (~> 1.0)
|
41
42
|
minitest (5.14.4)
|
42
|
-
parallel (1.
|
43
|
-
parser (3.0.
|
43
|
+
parallel (1.21.0)
|
44
|
+
parser (3.0.3.1)
|
44
45
|
ast (~> 2.4.1)
|
45
46
|
rack (2.2.3)
|
46
47
|
rainbow (3.0.0)
|
@@ -62,44 +63,43 @@ GEM
|
|
62
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
63
64
|
rspec-support (~> 3.10.0)
|
64
65
|
rspec-support (3.10.0)
|
65
|
-
rubocop (1.
|
66
|
+
rubocop (1.23.0)
|
66
67
|
parallel (~> 1.10)
|
67
68
|
parser (>= 3.0.0.0)
|
68
69
|
rainbow (>= 2.2.2, < 4.0)
|
69
70
|
regexp_parser (>= 1.8, < 3.0)
|
70
71
|
rexml
|
71
|
-
rubocop-ast (>= 1.
|
72
|
+
rubocop-ast (>= 1.12.0, < 2.0)
|
72
73
|
ruby-progressbar (~> 1.7)
|
73
74
|
unicode-display_width (>= 1.4.0, < 3.0)
|
74
|
-
rubocop-ast (1.
|
75
|
-
parser (>=
|
76
|
-
rubocop-performance (1.
|
77
|
-
rubocop (>=
|
75
|
+
rubocop-ast (1.13.0)
|
76
|
+
parser (>= 3.0.1.1)
|
77
|
+
rubocop-performance (1.12.0)
|
78
|
+
rubocop (>= 1.7.0, < 2.0)
|
78
79
|
rubocop-ast (>= 0.4.0)
|
79
|
-
rubocop-rails (2.
|
80
|
+
rubocop-rails (2.12.4)
|
80
81
|
activesupport (>= 4.2.0)
|
81
82
|
rack (>= 1.1)
|
82
|
-
rubocop (>=
|
83
|
-
rubocop-rake (0.
|
84
|
-
rubocop
|
85
|
-
rubocop-rspec (2.2.0)
|
83
|
+
rubocop (>= 1.7.0, < 2.0)
|
84
|
+
rubocop-rake (0.6.0)
|
86
85
|
rubocop (~> 1.0)
|
87
|
-
|
86
|
+
rubocop-rspec (2.6.0)
|
87
|
+
rubocop (~> 1.19)
|
88
88
|
ruby-progressbar (1.11.0)
|
89
89
|
simplecov (0.18.5)
|
90
90
|
docile (~> 1.1)
|
91
91
|
simplecov-html (~> 0.11)
|
92
92
|
simplecov-html (0.12.3)
|
93
93
|
slop (3.6.0)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
unicode-display_width (2.0.0)
|
94
|
+
tzinfo (2.0.4)
|
95
|
+
concurrent-ruby (~> 1.0)
|
96
|
+
unicode-display_width (2.1.0)
|
98
97
|
values (1.8.0)
|
99
98
|
yard (0.9.26)
|
100
99
|
yard-doctest (0.1.17)
|
101
100
|
minitest
|
102
101
|
yard
|
102
|
+
zeitwerk (2.5.1)
|
103
103
|
|
104
104
|
PLATFORMS
|
105
105
|
ruby
|
@@ -117,4 +117,4 @@ DEPENDENCIES
|
|
117
117
|
yard-doctest (~> 0.1)
|
118
118
|
|
119
119
|
BUNDLED WITH
|
120
|
-
2.2.
|
120
|
+
2.2.32
|
data/README.md
CHANGED
@@ -112,38 +112,38 @@ For more usage details, see our detailed [documentation](#documentation).
|
|
112
112
|
|
113
113
|
## Benchmarks
|
114
114
|
|
115
|
-
Benchmarks are run in GitHub Actions, and the tables below are updated with every code change. **Values >1.00x represent how much _slower_ each gem’s memoized value retrieval is than the latest commit of `MemoWise`**, according to [`benchmark-ips`](https://github.com/evanphx/benchmark-ips) (2.9.
|
115
|
+
Benchmarks are run in GitHub Actions, and the tables below are updated with every code change. **Values >1.00x represent how much _slower_ each gem’s memoized value retrieval is than the latest commit of `MemoWise`**, according to [`benchmark-ips`](https://github.com/evanphx/benchmark-ips) (2.9.2).
|
116
116
|
|
117
|
-
Results using Ruby 3.0.
|
117
|
+
Results using Ruby 3.0.3:
|
118
118
|
|
119
119
|
|Method arguments|`Dry::Core`\* (0.7.1)|`Memery` (1.4.0)|
|
120
120
|
|--|--|--|
|
121
|
-
|`()` (none)|1.
|
122
|
-
|`(a)`|2.
|
123
|
-
|`(a, b)`|0.
|
124
|
-
|`(a:)`|2.
|
125
|
-
|`(a:, b:)`|0.
|
126
|
-
|`(a, b:)`|0.
|
127
|
-
|`(a, *args)`|0.
|
128
|
-
|`(a:, **kwargs)`|0.
|
129
|
-
|`(a, *args, b:, **kwargs)`|0.61x|1.
|
121
|
+
|`()` (none)|1.36x|19.42x|
|
122
|
+
|`(a)`|2.47x|11.39x|
|
123
|
+
|`(a, b)`|0.44x|2.16x|
|
124
|
+
|`(a:)`|2.30x|22.89x|
|
125
|
+
|`(a:, b:)`|0.47x|4.54x|
|
126
|
+
|`(a, b:)`|0.47x|4.33x|
|
127
|
+
|`(a, *args)`|0.83x|2.09x|
|
128
|
+
|`(a:, **kwargs)`|0.76x|2.85x|
|
129
|
+
|`(a, *args, b:, **kwargs)`|0.61x|1.55x|
|
130
130
|
|
131
131
|
\* `Dry::Core`
|
132
132
|
[may cause incorrect behavior caused by hash collisions](https://github.com/dry-rb/dry-core/issues/63).
|
133
133
|
|
134
|
-
Results using Ruby 2.7.
|
134
|
+
Results using Ruby 2.7.5 (because these gems raise errors in Ruby 3.x):
|
135
135
|
|
136
136
|
|Method arguments|`DDMemoize` (1.0.0)|`Memoist` (0.16.2)|`Memoized` (1.0.2)|`Memoizer` (1.0.3)|
|
137
137
|
|--|--|--|--|--|
|
138
|
-
|`()` (none)|
|
139
|
-
|`(a)`|
|
140
|
-
|`(a, b)`|3.
|
141
|
-
|`(a:)`|
|
142
|
-
|`(a:, b:)`|5.
|
143
|
-
|`(a, b:)`|
|
144
|
-
|`(a, *args)`|3.
|
145
|
-
|`(a:, **kwargs)`|2.
|
146
|
-
|`(a, *args, b:, **kwargs)`|2.05x|1.
|
138
|
+
|`()` (none)|36.84x|3.56x|1.68x|4.19x|
|
139
|
+
|`(a)`|27.50x|18.85x|13.97x|15.99x|
|
140
|
+
|`(a, b)`|3.27x|2.34x|1.85x|2.05x|
|
141
|
+
|`(a:)`|37.22x|30.09x|25.57x|27.28x|
|
142
|
+
|`(a:, b:)`|5.25x|4.38x|3.80x|4.02x|
|
143
|
+
|`(a, b:)`|5.08x|4.15x|3.56x|3.78x|
|
144
|
+
|`(a, *args)`|3.17x|2.32x|1.96x|2.01x|
|
145
|
+
|`(a:, **kwargs)`|2.87x|2.42x|2.10x|2.21x|
|
146
|
+
|`(a, *args, b:, **kwargs)`|2.05x|1.76x|1.63x|1.65x|
|
147
147
|
|
148
148
|
You can run benchmarks yourself with:
|
149
149
|
|
@@ -156,6 +156,24 @@ $ bundle exec ruby benchmarks.rb
|
|
156
156
|
If your results differ from what's posted here,
|
157
157
|
[let us know](https://github.com/panorama-ed/memo_wise/issues/new)!
|
158
158
|
|
159
|
+
## Thread Safety
|
160
|
+
|
161
|
+
MemoWise makes the following **thread safety** guarantees on all supported Ruby
|
162
|
+
versions:
|
163
|
+
|
164
|
+
1. **Before** a value has been memoized
|
165
|
+
|
166
|
+
* Contended calls from multiple threads...
|
167
|
+
* May each call the original method
|
168
|
+
* May return different valid results (when the method is nondeterministic,
|
169
|
+
like `rand`)
|
170
|
+
* Will memoize exactly one valid return value
|
171
|
+
|
172
|
+
2. **After** a value has been memoized
|
173
|
+
|
174
|
+
* Contended calls from multiple threads...
|
175
|
+
* Always return the same memoized value
|
176
|
+
|
159
177
|
## Documentation
|
160
178
|
|
161
179
|
### Documentation is Automatically Generated
|
@@ -250,10 +268,12 @@ Then carry out these steps:
|
|
250
268
|
- Add an entry for the upcoming version _x.y.z_
|
251
269
|
- Add a link for this version's comparison to the bottom of `CHANGELOG.md`
|
252
270
|
- Move content from _Unreleased_ to the upcoming version _x.y.z_
|
271
|
+
- Change _Unreleased_ section to say `- Nothing yet!`
|
253
272
|
- Commit with title `Update CHANGELOG.md for x.y.z`
|
254
273
|
|
255
274
|
2. Update `lib/memo_wise/version.rb`
|
256
275
|
- Replace with upcoming version _x.y.z_
|
276
|
+
- Run `bundle install` to update `Gemfile.lock`
|
257
277
|
- Commit with title `Bump version to x.y.z`
|
258
278
|
|
259
279
|
3. `bundle exec rake release`
|
data/benchmarks/Gemfile
CHANGED
@@ -175,58 +175,6 @@ module MemoWise
|
|
175
175
|
end
|
176
176
|
end
|
177
177
|
|
178
|
-
# Increment the class's method index counter, and return an index to use for
|
179
|
-
# the given method name.
|
180
|
-
#
|
181
|
-
# @param klass [Class]
|
182
|
-
# Original class on which a method is being memoized
|
183
|
-
#
|
184
|
-
# @param method_name [Symbol]
|
185
|
-
# The name of the method being memoized
|
186
|
-
#
|
187
|
-
# @return [Integer]
|
188
|
-
# The index within `@_memo_wise` to store the method's memoized results
|
189
|
-
def self.next_index!(klass, method_name)
|
190
|
-
# `@_memo_wise_indices` stores the `@_memo_wise` indices of different
|
191
|
-
# method names. We only use this data structure when resetting or
|
192
|
-
# presetting memoization. It looks like:
|
193
|
-
# {
|
194
|
-
# single_arg_method_name: 0,
|
195
|
-
# other_single_arg_method_name: 1
|
196
|
-
# }
|
197
|
-
memo_wise_indices = klass.instance_variable_get(:@_memo_wise_indices)
|
198
|
-
memo_wise_indices ||= klass.instance_variable_set(:@_memo_wise_indices, {})
|
199
|
-
|
200
|
-
# When a parent and child class both use `class << self` to define
|
201
|
-
# memoized class methods, the child class' singleton is not considered a
|
202
|
-
# descendent of the parent class' singleton. Because we store the index
|
203
|
-
# counter as a class variable that can be shared up the inheritance chain,
|
204
|
-
# we want to detect this case and store it on the original class instead
|
205
|
-
# of the singleton to make the counter shared correctly.
|
206
|
-
counter_class = klass.singleton_class? ? original_class_from_singleton(klass) : klass
|
207
|
-
|
208
|
-
# We use a class variable for tracking the index to make this work with
|
209
|
-
# inheritance structures. When a parent and child class both use
|
210
|
-
# MemoWise, we want the child class's index to not "reset" back to 0 and
|
211
|
-
# overwrite the behavior of a memoized parent method. Using a class
|
212
|
-
# variable will share the index data between parent and child classes.
|
213
|
-
#
|
214
|
-
# However, we don't use a class variable for `@_memo_wise_indices`
|
215
|
-
# because we want to allow instance and class methods with the same name
|
216
|
-
# to both be memoized, and using a class variable would share that index
|
217
|
-
# data between them.
|
218
|
-
index = if counter_class.class_variable_defined?(:@@_memo_wise_index_counter)
|
219
|
-
counter_class.class_variable_get(:@@_memo_wise_index_counter)
|
220
|
-
else
|
221
|
-
0
|
222
|
-
end
|
223
|
-
|
224
|
-
memo_wise_indices[method_name] = index
|
225
|
-
counter_class.class_variable_set(:@@_memo_wise_index_counter, index + 1) # rubocop:disable Style/ClassVars
|
226
|
-
|
227
|
-
index
|
228
|
-
end
|
229
|
-
|
230
178
|
# Convention we use for renaming the original method when we replace with
|
231
179
|
# the memoized version in {MemoWise.memo_wise}.
|
232
180
|
#
|
@@ -240,25 +188,20 @@ module MemoWise
|
|
240
188
|
:"_memo_wise_original_#{method_name}"
|
241
189
|
end
|
242
190
|
|
243
|
-
# @param target [Class, Module]
|
244
|
-
# The class to which we are prepending MemoWise to provide memoization;
|
245
|
-
# the `InternalAPI` *instance* methods will refer to this `target` class.
|
246
|
-
def initialize(target)
|
247
|
-
@target = target
|
248
|
-
end
|
249
|
-
|
250
|
-
# @return [Class, Module]
|
251
|
-
attr_reader :target
|
252
|
-
|
253
191
|
# @param method_name [Symbol] the name of the memoized method
|
254
192
|
# @return [Integer] the array index in `@_memo_wise_indices` to use to find
|
255
193
|
# the memoization data for the given method
|
256
|
-
def index(method_name)
|
257
|
-
target_class
|
194
|
+
def self.index(target, method_name)
|
195
|
+
klass = target_class(target)
|
196
|
+
indices = klass.instance_variable_get(:@_memo_wise_indices)
|
197
|
+
indices&.[](method_name) || next_index!(klass, method_name)
|
258
198
|
end
|
259
199
|
|
260
200
|
# Returns visibility of an instance method defined on class `target`.
|
261
201
|
#
|
202
|
+
# @param target [Class, Module]
|
203
|
+
# The class to which we are prepending MemoWise to provide memoization.
|
204
|
+
#
|
262
205
|
# @param method_name [Symbol]
|
263
206
|
# Name of existing *instance* method find the visibility of.
|
264
207
|
#
|
@@ -269,7 +212,7 @@ module MemoWise
|
|
269
212
|
# Raises `ArgumentError` unless `method_name` is a `Symbol` corresponding
|
270
213
|
# to an existing **instance** method defined on `klass`.
|
271
214
|
#
|
272
|
-
def method_visibility(method_name)
|
215
|
+
def self.method_visibility(target, method_name)
|
273
216
|
if target.private_method_defined?(method_name)
|
274
217
|
:private
|
275
218
|
elsif target.protected_method_defined?(method_name)
|
@@ -283,20 +226,23 @@ module MemoWise
|
|
283
226
|
|
284
227
|
# Validates that {.memo_wise} has already been called on `method_name`.
|
285
228
|
#
|
229
|
+
# @param target [Class, Module]
|
230
|
+
# The class to which we are prepending MemoWise to provide memoization.
|
231
|
+
#
|
286
232
|
# @param method_name [Symbol]
|
287
233
|
# Name of method to validate has already been setup with {.memo_wise}
|
288
|
-
def validate_memo_wised!(method_name)
|
289
|
-
original_name =
|
234
|
+
def self.validate_memo_wised!(target, method_name)
|
235
|
+
original_name = original_memo_wised_name(method_name)
|
290
236
|
|
291
|
-
unless target_class.private_method_defined?(original_name)
|
237
|
+
unless target_class(target).private_method_defined?(original_name)
|
292
238
|
raise ArgumentError, "#{method_name} is not a memo_wised method"
|
293
239
|
end
|
294
240
|
end
|
295
241
|
|
296
|
-
|
297
|
-
|
242
|
+
# @param target [Class, Module]
|
243
|
+
# The class to which we are prepending MemoWise to provide memoization.
|
298
244
|
# @return [Class] where we look for method definitions
|
299
|
-
def target_class
|
245
|
+
def self.target_class(target)
|
300
246
|
if target.instance_of?(Class)
|
301
247
|
# A class's methods are defined in its singleton class
|
302
248
|
target.singleton_class
|
@@ -305,5 +251,36 @@ module MemoWise
|
|
305
251
|
target.class
|
306
252
|
end
|
307
253
|
end
|
254
|
+
private_class_method :target_class
|
255
|
+
|
256
|
+
# Increment the class's method index counter, and return an index to use for
|
257
|
+
# the given method name.
|
258
|
+
#
|
259
|
+
# @param klass [Class]
|
260
|
+
# Original class on which a method is being memoized
|
261
|
+
#
|
262
|
+
# @param method_name [Symbol]
|
263
|
+
# The name of the method being memoized
|
264
|
+
#
|
265
|
+
# @return [Integer]
|
266
|
+
# The index within `@_memo_wise` to store the method's memoized results
|
267
|
+
def self.next_index!(klass, method_name)
|
268
|
+
# `@_memo_wise_indices` stores the `@_memo_wise` indices of different
|
269
|
+
# method names. We only use this data structure when resetting or
|
270
|
+
# presetting memoization. It looks like:
|
271
|
+
# {
|
272
|
+
# single_arg_method_name: 0,
|
273
|
+
# other_single_arg_method_name: 1
|
274
|
+
# }
|
275
|
+
memo_wise_indices = klass.instance_variable_get(:@_memo_wise_indices)
|
276
|
+
memo_wise_indices ||= klass.instance_variable_set(:@_memo_wise_indices, {})
|
277
|
+
|
278
|
+
index = klass.instance_variable_get(:@_memo_wise_index_counter) || 0
|
279
|
+
memo_wise_indices[method_name] = index
|
280
|
+
klass.instance_variable_set(:@_memo_wise_index_counter, index + 1)
|
281
|
+
|
282
|
+
index
|
283
|
+
end
|
284
|
+
private_class_method :next_index!
|
308
285
|
end
|
309
286
|
end
|
data/lib/memo_wise/version.rb
CHANGED
data/lib/memo_wise.rb
CHANGED
@@ -169,8 +169,7 @@ module MemoWise
|
|
169
169
|
|
170
170
|
raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol)
|
171
171
|
|
172
|
-
|
173
|
-
visibility = api.method_visibility(method_name)
|
172
|
+
visibility = MemoWise::InternalAPI.method_visibility(klass, method_name)
|
174
173
|
original_memo_wised_name = MemoWise::InternalAPI.original_memo_wised_name(method_name)
|
175
174
|
method = klass.instance_method(method_name)
|
176
175
|
|
@@ -178,37 +177,75 @@ module MemoWise
|
|
178
177
|
klass.send(:private, original_memo_wised_name)
|
179
178
|
|
180
179
|
method_arguments = MemoWise::InternalAPI.method_arguments(method)
|
181
|
-
index = MemoWise::InternalAPI.next_index!(klass, method_name)
|
182
180
|
|
183
181
|
case method_arguments
|
184
182
|
when MemoWise::InternalAPI::NONE
|
185
183
|
# Zero-arg methods can use simpler/more performant logic because the
|
186
184
|
# hash key is just the method name.
|
187
|
-
klass.
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
185
|
+
klass.send(:define_method, method_name) do # Ruby 2.4's `define_method` is private in some cases
|
186
|
+
index = MemoWise::InternalAPI.index(self, method_name)
|
187
|
+
klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
|
188
|
+
def #{method_name}
|
189
|
+
if @_memo_wise_sentinels[#{index}]
|
190
|
+
@_memo_wise[#{index}]
|
191
|
+
else
|
192
|
+
ret = @_memo_wise[#{index}] = #{original_memo_wised_name}
|
193
|
+
@_memo_wise_sentinels[#{index}] = true
|
194
|
+
ret
|
195
|
+
end
|
195
196
|
end
|
196
|
-
|
197
|
-
|
197
|
+
HEREDOC
|
198
|
+
|
199
|
+
klass.send(visibility, method_name)
|
200
|
+
send(method_name)
|
201
|
+
end
|
198
202
|
when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL, MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD
|
199
203
|
key = method.parameters.first.last
|
204
|
+
# NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use
|
205
|
+
# `define_method(...) do |*args, **kwargs|`. Instead we must use the
|
206
|
+
# simpler `|*args|` pattern. We can't just do this always though
|
207
|
+
# because Ruby 2.7 and above require `|*args, **kwargs|` to work
|
208
|
+
# correctly.
|
209
|
+
# See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26
|
210
|
+
# :nocov:
|
211
|
+
if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby"
|
212
|
+
klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases
|
213
|
+
index = MemoWise::InternalAPI.index(self, method_name)
|
214
|
+
klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
|
215
|
+
def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
|
216
|
+
_memo_wise_hash = (@_memo_wise[#{index}] ||= {})
|
217
|
+
_memo_wise_output = _memo_wise_hash[#{key}]
|
218
|
+
if _memo_wise_output || _memo_wise_hash.key?(#{key})
|
219
|
+
_memo_wise_output
|
220
|
+
else
|
221
|
+
_memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
|
222
|
+
end
|
223
|
+
end
|
224
|
+
HEREDOC
|
200
225
|
|
201
|
-
|
202
|
-
|
203
|
-
_memo_wise_hash = (@_memo_wise[#{index}] ||= {})
|
204
|
-
_memo_wise_output = _memo_wise_hash[#{key}]
|
205
|
-
if _memo_wise_output || _memo_wise_hash.key?(#{key})
|
206
|
-
_memo_wise_output
|
207
|
-
else
|
208
|
-
_memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
|
209
|
-
end
|
226
|
+
klass.send(visibility, method_name)
|
227
|
+
send(method_name, *args)
|
210
228
|
end
|
211
|
-
|
229
|
+
# :nocov:
|
230
|
+
else
|
231
|
+
klass.define_method(method_name) do |*args, **kwargs|
|
232
|
+
index = MemoWise::InternalAPI.index(self, method_name)
|
233
|
+
klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
|
234
|
+
def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
|
235
|
+
_memo_wise_hash = (@_memo_wise[#{index}] ||= {})
|
236
|
+
_memo_wise_output = _memo_wise_hash[#{key}]
|
237
|
+
if _memo_wise_output || _memo_wise_hash.key?(#{key})
|
238
|
+
_memo_wise_output
|
239
|
+
else
|
240
|
+
_memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
|
241
|
+
end
|
242
|
+
end
|
243
|
+
HEREDOC
|
244
|
+
|
245
|
+
klass.send(visibility, method_name)
|
246
|
+
send(method_name, *args, **kwargs)
|
247
|
+
end
|
248
|
+
end
|
212
249
|
# MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT,
|
213
250
|
# MemoWise::InternalAPI::DOUBLE_SPLAT, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
|
214
251
|
else
|
@@ -224,18 +261,54 @@ module MemoWise
|
|
224
261
|
# consistent performance. In general, this should still be faster for
|
225
262
|
# truthy results because `Hash#[]` generally performs hash lookups
|
226
263
|
# faster than `Hash#fetch`.
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
264
|
+
#
|
265
|
+
# NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use
|
266
|
+
# `define_method(...) do |*args, **kwargs|`. Instead we must use the
|
267
|
+
# simpler `|*args|` pattern. We can't just do this always though
|
268
|
+
# because Ruby 2.7 and above require `|*args, **kwargs|` to work
|
269
|
+
# correctly.
|
270
|
+
# See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26
|
271
|
+
# :nocov:
|
272
|
+
if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby"
|
273
|
+
klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases
|
274
|
+
index = MemoWise::InternalAPI.index(self, method_name)
|
275
|
+
klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
|
276
|
+
def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
|
277
|
+
_memo_wise_hash = (@_memo_wise[#{index}] ||= {})
|
278
|
+
_memo_wise_key = #{MemoWise::InternalAPI.key_str(method)}
|
279
|
+
_memo_wise_output = _memo_wise_hash[_memo_wise_key]
|
280
|
+
if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key)
|
281
|
+
_memo_wise_output
|
282
|
+
else
|
283
|
+
_memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
|
284
|
+
end
|
285
|
+
end
|
286
|
+
HEREDOC
|
287
|
+
|
288
|
+
klass.send(visibility, method_name)
|
289
|
+
send(method_name, *args)
|
237
290
|
end
|
238
|
-
|
291
|
+
# :nocov:
|
292
|
+
else # Ruby 2.7 and above break with (*args)
|
293
|
+
klass.define_method(method_name) do |*args, **kwargs|
|
294
|
+
index = MemoWise::InternalAPI.index(self, method_name)
|
295
|
+
klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
|
296
|
+
def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
|
297
|
+
_memo_wise_hash = (@_memo_wise[#{index}] ||= {})
|
298
|
+
_memo_wise_key = #{MemoWise::InternalAPI.key_str(method)}
|
299
|
+
_memo_wise_output = _memo_wise_hash[_memo_wise_key]
|
300
|
+
if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key)
|
301
|
+
_memo_wise_output
|
302
|
+
else
|
303
|
+
_memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
|
304
|
+
end
|
305
|
+
end
|
306
|
+
HEREDOC
|
307
|
+
|
308
|
+
klass.send(visibility, method_name)
|
309
|
+
send(method_name, *args, **kwargs)
|
310
|
+
end
|
311
|
+
end
|
239
312
|
end
|
240
313
|
|
241
314
|
klass.send(visibility, method_name)
|
@@ -440,12 +513,11 @@ module MemoWise
|
|
440
513
|
def preset_memo_wise(method_name, *args, **kwargs)
|
441
514
|
raise ArgumentError, "Pass a block as the value to preset for #{method_name}, #{args}" unless block_given?
|
442
515
|
|
443
|
-
|
444
|
-
api.validate_memo_wised!(method_name)
|
516
|
+
MemoWise::InternalAPI.validate_memo_wised!(self, method_name)
|
445
517
|
|
446
|
-
method = method(method_name)
|
518
|
+
method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name))
|
447
519
|
method_arguments = MemoWise::InternalAPI.method_arguments(method)
|
448
|
-
index =
|
520
|
+
index = MemoWise::InternalAPI.index(self, method_name)
|
449
521
|
|
450
522
|
if method_arguments == MemoWise::InternalAPI::NONE
|
451
523
|
@_memo_wise_sentinels[index] = true
|
@@ -548,12 +620,11 @@ module MemoWise
|
|
548
620
|
raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol)
|
549
621
|
raise ArgumentError, "#{method_name} is not a defined method" unless respond_to?(method_name, true)
|
550
622
|
|
551
|
-
|
552
|
-
api.validate_memo_wised!(method_name)
|
623
|
+
MemoWise::InternalAPI.validate_memo_wised!(self, method_name)
|
553
624
|
|
554
|
-
method = method(method_name)
|
625
|
+
method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name))
|
555
626
|
method_arguments = MemoWise::InternalAPI.method_arguments(method)
|
556
|
-
index =
|
627
|
+
index = MemoWise::InternalAPI.index(self, method_name)
|
557
628
|
|
558
629
|
case method_arguments
|
559
630
|
when MemoWise::InternalAPI::NONE
|
data/memo_wise.gemspec
CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
|
|
36
36
|
spec.require_paths = ["lib"]
|
37
37
|
|
38
38
|
spec.metadata = {
|
39
|
+
"rubygems_mfa_required" => "true",
|
39
40
|
"changelog_uri" => "https://github.com/panorama-ed/memo_wise/blob/main/CHANGELOG.md",
|
40
41
|
"source_code_uri" => "https://github.com/panorama-ed/memo_wise"
|
41
42
|
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memo_wise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Panorama Education
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2021-
|
14
|
+
date: 2021-12-10 00:00:00.000000000 Z
|
15
15
|
dependencies: []
|
16
16
|
description:
|
17
17
|
email:
|
@@ -52,6 +52,7 @@ homepage: https://github.com/panorama-ed/memo_wise
|
|
52
52
|
licenses:
|
53
53
|
- MIT
|
54
54
|
metadata:
|
55
|
+
rubygems_mfa_required: 'true'
|
55
56
|
changelog_uri: https://github.com/panorama-ed/memo_wise/blob/main/CHANGELOG.md
|
56
57
|
source_code_uri: https://github.com/panorama-ed/memo_wise
|
57
58
|
post_install_message:
|
@@ -69,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
70
|
- !ruby/object:Gem::Version
|
70
71
|
version: '0'
|
71
72
|
requirements: []
|
72
|
-
rubygems_version: 3.2.
|
73
|
+
rubygems_version: 3.2.3
|
73
74
|
signing_key:
|
74
75
|
specification_version: 4
|
75
76
|
summary: The wise choice for Ruby memoization
|