memo_wise 1.6.0 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aee2439aaa86140659e102e2269e4d773e34a933e73ff604b449918948b9cd29
4
- data.tar.gz: bff512646d578b936d45267408be8681479c7316fa7ae16c683fc3b47261577a
3
+ metadata.gz: '009e350b06faaa529d704c4386b7443719d9a1faa2a58d9532e33028545ae938'
4
+ data.tar.gz: b7c41458ffb2ff88afc3f731c6c9e04e5133e2ac8830a24dfc1d78409802831d
5
5
  SHA512:
6
- metadata.gz: a820daf7cbc735f0bc586cc7e2b73c507f01592f8666ed3837b83584537466b4d06aee3202aa7c31b4545558f3e24a45ccc4063e315479fbbc4f3ece1606f7d8
7
- data.tar.gz: 6349b73c2cec18df74259b79dceef15f7f9303001deffb94146c00abcc5e9f0d6be9ab82c0aa0adab0dc528dd75de4e5c8450df389f64c88c24bea50dead15ce
6
+ metadata.gz: 72500c89882f08671756f564205aef35e8546edb24d416e98444d5368c5e531c9e815791ba7ece1385e7e1405aaf1395a9c930dd8c711c2a70563818c2d1d4f6
7
+ data.tar.gz: 95418a3b09950182d433b037250afcd5142c323fa23dad4be256984a76bb4ebe0c3a831b0ba1fd8205767a0b3ee4566685a3687cb0042e59bd9bc03030654fbe
@@ -12,10 +12,12 @@ jobs:
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
15
- ruby: [jruby, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, truffleruby-head]
15
+ # Due to https://github.com/actions/runner/issues/849, we have to use
16
+ # quotes for '3.0' -- without quotes, CI sees '3' and runs the latest.
17
+ ruby: [2.4, 2.5, 2.6, 2.7, '3.0', 3.1, jruby, truffleruby-head]
16
18
  runs-on: ubuntu-latest
17
19
  steps:
18
- - uses: actions/checkout@v2
20
+ - uses: actions/checkout@v3
19
21
 
20
22
  # Conditionally configure bundler via environment variables as advised
21
23
  # * https://github.com/ruby/setup-ruby#bundle-config
@@ -33,6 +35,13 @@ jobs:
33
35
 
34
36
  - run: bundle exec rspec
35
37
 
38
+ - uses: codecov/codecov-action@v2
39
+ with:
40
+ files: ./coverage/coverage.xml
41
+ fail_ci_if_error: true # optional (default = false)
42
+ verbose: true # optional (default = false)
43
+ if: matrix.ruby == 3.1
44
+
36
45
  - run: bundle exec rubocop
37
46
  if: matrix.ruby == 3.1
38
47
 
data/CHANGELOG.md CHANGED
@@ -7,7 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## Unreleased
9
9
 
10
- Nothing yet!
10
+ - Nothing yet!
11
+
12
+ ## [1.7.0] - 2022-04-04
13
+
14
+ ### Updated
15
+
16
+ - Optimize memoized lookups for methods with multiple required arguments
17
+ ([#276](https://github.com/panorama-ed/memo_wise/pull/276))
11
18
 
12
19
  ## [1.6.0] - 2022-01-24
13
20
 
@@ -131,7 +138,8 @@ Nothing yet!
131
138
  - Panolint
132
139
  - Dependabot setup
133
140
 
134
- [Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1.6.0...HEAD
141
+ [Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1.7.0...HEAD
142
+ [1.6.0]: https://github.com/panorama-ed/memo_wise/compare/v1.6.0...v1.7.0
135
143
  [1.6.0]: https://github.com/panorama-ed/memo_wise/compare/v1.5.0...v1.6.0
136
144
  [1.5.0]: https://github.com/panorama-ed/memo_wise/compare/v1.4.0...v1.5.0
137
145
  [1.4.0]: https://github.com/panorama-ed/memo_wise/compare/v1.3.0...v1.4.0
data/Gemfile CHANGED
@@ -7,14 +7,19 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
7
7
  gemspec
8
8
 
9
9
  group :test do
10
- gem "rspec", "~> 3.10"
10
+ gem "rspec", "~> 3.11"
11
11
  gem "values", "~> 1"
12
12
  end
13
13
 
14
14
  # Excluded from CI except on latest MRI Ruby, to reduce compatibility burden
15
15
  group :checks do
16
- gem "codecov"
17
16
  gem "panolint", github: "panorama-ed/panolint", branch: "main"
17
+
18
+ # Simplecov to generate coverage info
19
+ gem "simplecov", require: false
20
+
21
+ # Simplecov-cobertura to generate an xml coverage file to upload to Codecov
22
+ gem "simplecov-cobertura", require: false
18
23
  end
19
24
 
20
25
  # Excluded from CI except on latest MRI Ruby, to reduce compatibility burden
data/Gemfile.lock CHANGED
@@ -1,6 +1,6 @@
1
1
  GIT
2
2
  remote: https://github.com/panorama-ed/panolint.git
3
- revision: 5850a218b96da7dd196438fb65d41169cd6747e5
3
+ revision: 32da31ae800b7e16068b6495397cd98aa04c68a3
4
4
  branch: main
5
5
  specs:
6
6
  panolint (0.1.4)
@@ -14,82 +14,84 @@ GIT
14
14
  PATH
15
15
  remote: .
16
16
  specs:
17
- memo_wise (1.6.0)
17
+ memo_wise (1.7.0)
18
18
 
19
19
  GEM
20
20
  remote: https://rubygems.org/
21
21
  specs:
22
- activesupport (6.1.4.1)
22
+ activesupport (7.0.2.2)
23
23
  concurrent-ruby (~> 1.0, >= 1.0.2)
24
24
  i18n (>= 1.6, < 2)
25
25
  minitest (>= 5.1)
26
26
  tzinfo (~> 2.0)
27
- zeitwerk (~> 2.3)
28
27
  ansi (1.5.0)
29
28
  ast (2.4.2)
30
- brakeman (5.1.2)
31
- codecov (0.6.0)
32
- simplecov (>= 0.15, < 0.22)
29
+ brakeman (5.2.1)
33
30
  concurrent-ruby (1.1.9)
34
- diff-lcs (1.4.4)
35
- docile (1.3.5)
31
+ diff-lcs (1.5.0)
32
+ docile (1.4.0)
36
33
  dokaz (0.0.4)
37
34
  ansi
38
35
  rouge
39
36
  slop (~> 3)
40
- i18n (1.8.11)
37
+ i18n (1.10.0)
41
38
  concurrent-ruby (~> 1.0)
42
- minitest (5.14.4)
39
+ minitest (5.15.0)
43
40
  parallel (1.21.0)
44
- parser (3.1.0.0)
41
+ parser (3.1.1.0)
45
42
  ast (~> 2.4.1)
46
43
  rack (2.2.3)
47
- rainbow (3.0.0)
44
+ rainbow (3.1.1)
48
45
  rake (13.0.6)
49
46
  redcarpet (3.5.1)
50
- regexp_parser (2.2.0)
47
+ regexp_parser (2.2.1)
51
48
  rexml (3.2.5)
52
- rouge (3.26.0)
53
- rspec (3.10.0)
54
- rspec-core (~> 3.10.0)
55
- rspec-expectations (~> 3.10.0)
56
- rspec-mocks (~> 3.10.0)
57
- rspec-core (3.10.0)
58
- rspec-support (~> 3.10.0)
59
- rspec-expectations (3.10.0)
49
+ rouge (3.28.0)
50
+ rspec (3.11.0)
51
+ rspec-core (~> 3.11.0)
52
+ rspec-expectations (~> 3.11.0)
53
+ rspec-mocks (~> 3.11.0)
54
+ rspec-core (3.11.0)
55
+ rspec-support (~> 3.11.0)
56
+ rspec-expectations (3.11.0)
60
57
  diff-lcs (>= 1.2.0, < 2.0)
61
- rspec-support (~> 3.10.0)
62
- rspec-mocks (3.10.0)
58
+ rspec-support (~> 3.11.0)
59
+ rspec-mocks (3.11.0)
63
60
  diff-lcs (>= 1.2.0, < 2.0)
64
- rspec-support (~> 3.10.0)
65
- rspec-support (3.10.0)
66
- rubocop (1.24.1)
61
+ rspec-support (~> 3.11.0)
62
+ rspec-support (3.11.0)
63
+ rubocop (1.25.1)
67
64
  parallel (~> 1.10)
68
- parser (>= 3.0.0.0)
65
+ parser (>= 3.1.0.0)
69
66
  rainbow (>= 2.2.2, < 4.0)
70
67
  regexp_parser (>= 1.8, < 3.0)
71
68
  rexml
72
69
  rubocop-ast (>= 1.15.1, < 2.0)
73
70
  ruby-progressbar (~> 1.7)
74
71
  unicode-display_width (>= 1.4.0, < 3.0)
75
- rubocop-ast (1.15.1)
76
- parser (>= 3.0.1.1)
77
- rubocop-performance (1.12.0)
72
+ rubocop-ast (1.16.0)
73
+ parser (>= 3.1.1.0)
74
+ rubocop-performance (1.13.2)
78
75
  rubocop (>= 1.7.0, < 2.0)
79
76
  rubocop-ast (>= 0.4.0)
80
- rubocop-rails (2.12.4)
77
+ rubocop-rails (2.13.2)
81
78
  activesupport (>= 4.2.0)
82
79
  rack (>= 1.1)
83
80
  rubocop (>= 1.7.0, < 2.0)
84
81
  rubocop-rake (0.6.0)
85
82
  rubocop (~> 1.0)
86
- rubocop-rspec (2.6.0)
83
+ rubocop-rspec (2.9.0)
87
84
  rubocop (~> 1.19)
88
85
  ruby-progressbar (1.11.0)
89
- simplecov (0.18.5)
86
+ simplecov (0.21.2)
90
87
  docile (~> 1.1)
91
88
  simplecov-html (~> 0.11)
89
+ simplecov_json_formatter (~> 0.1)
90
+ simplecov-cobertura (2.1.0)
91
+ rexml
92
+ simplecov (~> 0.19)
92
93
  simplecov-html (0.12.3)
94
+ simplecov_json_formatter (0.1.4)
93
95
  slop (3.6.0)
94
96
  tzinfo (2.0.4)
95
97
  concurrent-ruby (~> 1.0)
@@ -101,22 +103,22 @@ GEM
101
103
  yard-doctest (0.1.17)
102
104
  minitest
103
105
  yard
104
- zeitwerk (2.5.1)
105
106
 
106
107
  PLATFORMS
107
108
  ruby
108
109
 
109
110
  DEPENDENCIES
110
- codecov
111
111
  dokaz
112
112
  memo_wise!
113
113
  panolint!
114
114
  rake
115
115
  redcarpet (~> 3.5)
116
- rspec (~> 3.10)
116
+ rspec (~> 3.11)
117
+ simplecov
118
+ simplecov-cobertura
117
119
  values (~> 1)
118
120
  yard (~> 0.9)
119
121
  yard-doctest (~> 0.1)
120
122
 
121
123
  BUNDLED WITH
122
- 2.2.32
124
+ 2.3.8
data/README.md CHANGED
@@ -8,7 +8,6 @@
8
8
  [![Tests](https://github.com/panorama-ed/memo_wise/workflows/Main/badge.svg)](https://github.com/panorama-ed/memo_wise/actions?query=workflow%3AMain)
9
9
  [![Code Coverage](https://codecov.io/gh/panorama-ed/memo_wise/branch/main/graph/badge.svg)](https://codecov.io/gh/panorama-ed/memo_wise/branches/main)
10
10
  [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/panorama-ed/memo_wise)
11
- [![Inline docs](http://inch-ci.org/github/panorama-ed/memo_wise.svg?branch=main)](http://inch-ci.org/github/panorama-ed/memo_wise)
12
11
  [![Gem Version](https://img.shields.io/gem/v/memo_wise.svg)](https://rubygems.org/gems/memo_wise)
13
12
  [![Gem Downloads](https://img.shields.io/gem/dt/memo_wise.svg)](https://rubygems.org/gems/memo_wise)
14
13
 
@@ -115,19 +114,19 @@ For more usage details, see our detailed [documentation](#documentation).
115
114
 
116
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).
117
116
 
118
- Results using Ruby 3.1.0:
117
+ Results using Ruby 3.1.1:
119
118
 
120
- |Method arguments|`Dry::Core` (0.7.1)|`Memery` (1.4.0)|
119
+ |Method arguments|`Dry::Core`\* (0.7.1)|`Memery` (1.4.0)|
121
120
  |--|--|--|
122
- |`()` (none)|1.09x|11.80x|
123
- |`(a)`|1.64x|9.74x|
124
- |`(a, b)`|0.34x|2.00x|
125
- |`(a:)`|1.49x|18.84x|
126
- |`(a:, b:)`|0.35x|4.26x|
127
- |`(a, b:)`|0.35x|4.18x|
128
- |`(a, *args)`|0.83x|1.89x|
129
- |`(a:, **kwargs)`|0.84x|3.30x|
130
- |`(a, *args, b:, **kwargs)`|0.54x|1.47x|
121
+ |`()` (none)|1.11x|12.24x|
122
+ |`(a)`|1.71x|9.55x|
123
+ |`(a, b)`|1.27x|6.95x|
124
+ |`(a:)`|1.58x|18.25x|
125
+ |`(a:, b:)`|1.19x|13.31x|
126
+ |`(a, b:)`|1.22x|13.29x|
127
+ |`(a, *args)`|0.86x|1.84x|
128
+ |`(a:, **kwargs)`|0.83x|3.15x|
129
+ |`(a, *args, b:, **kwargs)`|0.76x|1.92x|
131
130
 
132
131
  \* `Dry::Core`
133
132
  [may cause incorrect behavior caused by hash collisions](https://github.com/dry-rb/dry-core/issues/63).
@@ -136,15 +135,15 @@ Results using Ruby 2.7.5 (because these gems raise errors in Ruby 3.x):
136
135
 
137
136
  |Method arguments|`DDMemoize` (1.0.0)|`Memoist` (0.16.2)|`Memoized` (1.0.2)|`Memoizer` (1.0.3)|
138
137
  |--|--|--|--|--|
139
- |`()` (none)|24.30x|2.57x|1.19x|2.98x|
140
- |`(a)`|21.68x|14.63x|11.13x|12.71x|
141
- |`(a, b)`|3.18x|2.36x|1.86x|2.06x|
142
- |`(a:)`|30.62x|24.52x|21.44x|22.61x|
143
- |`(a:, b:)`|5.25x|4.40x|3.80x|4.04x|
144
- |`(a, b:)`|4.91x|4.06x|3.55x|3.83x|
145
- |`(a, *args)`|3.10x|2.31x|1.96x|1.98x|
146
- |`(a:, **kwargs)`|2.87x|2.40x|2.09x|2.20x|
147
- |`(a, *args, b:, **kwargs)`|2.08x|1.82x|1.67x|1.70x|
138
+ |`()` (none)|24.22x|2.48x|1.22x|3.08x|
139
+ |`(a)`|20.38x|14.06x|10.85x|12.26x|
140
+ |`(a, b)`|17.48x|12.67x|10.07x|11.32x|
141
+ |`(a:)`|29.72x|24.26x|21.04x|21.72x|
142
+ |`(a:, b:)`|24.17x|20.17x|17.81x|18.85x|
143
+ |`(a, b:)`|24.20x|20.15x|17.51x|18.05x|
144
+ |`(a, *args)`|3.11x|2.23x|1.95x|2.03x|
145
+ |`(a:, **kwargs)`|2.96x|2.46x|2.17x|2.28x|
146
+ |`(a, *args, b:, **kwargs)`|2.17x|1.86x|1.76x|1.76x|
148
147
 
149
148
  You can run benchmarks yourself with:
150
149
 
data/benchmarks/Gemfile CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
6
+
5
7
  ruby ">= 2.7.5"
6
8
 
7
- gem "benchmark-ips", "2.9.2"
9
+ gem "benchmark-ips", "2.10.0"
8
10
 
9
11
  if RUBY_VERSION > "3"
10
12
  gem "dry-core", "0.7.1"
@@ -16,4 +18,4 @@ else
16
18
  gem "memoizer", "1.0.3"
17
19
  end
18
20
 
19
- gem "memo_wise", path: ".."
21
+ gem "memo_wise", github: "panorama-ed/memo_wise", branch: "main"
@@ -3,7 +3,26 @@
3
3
  require "benchmark/ips"
4
4
 
5
5
  require "tempfile"
6
- require "memo_wise"
6
+
7
+ github_memo_wise_path = Gem.loaded_specs["memo_wise"].full_gem_path
8
+
9
+ # This string is both used for temp filepaths necessary to separate the GitHub
10
+ # version of MemoWise and the local version, and used for the reported results
11
+ GITHUB_MAIN = "MemoWise_GitHubMain"
12
+
13
+ # We download a the main branch of MemoWise on GitHub into a tmp directory to
14
+ # compare against the local version when we run benchmarks
15
+ Dir.mktmpdir do |directory|
16
+ Dir["#{github_memo_wise_path}/lib/**/*.rb"].each do |file|
17
+ Tempfile.open([File.basename(file)[0..-4], ".rb"], directory) do |tempfile|
18
+ tempfile.write(File.read(file).gsub("MemoWise", GITHUB_MAIN))
19
+ tempfile.rewind
20
+ require tempfile.path
21
+ end
22
+ end
23
+ end
24
+
25
+ require_relative "../lib/memo_wise"
7
26
 
8
27
  # Some gems do not yet work in Ruby 3 so we only require them if they're loaded
9
28
  # in the Gemfile.
@@ -51,6 +70,7 @@ end
51
70
  # NOTE: Some gems do not yet work in Ruby 3 so we only test with them if they've
52
71
  # been `require`d.
53
72
  BENCHMARK_GEMS = [
73
+ BenchmarkGem.new(MemoWise_GitHubMain, "prepend #{GITHUB_MAIN}", :memo_wise),
54
74
  BenchmarkGem.new(MemoWise, "prepend MemoWise", :memo_wise),
55
75
  (BenchmarkGem.new(DDMemoize, "DDMemoize.activate(self)", :memoize) if defined?(DDMemoize)),
56
76
  (BenchmarkGem.new(Dry::Core, "include Dry::Core::Memoizable", :memoize) if defined?(Dry::Core)),
@@ -187,7 +207,7 @@ benchmark_lambdas = [
187
207
 
188
208
  # We benchmark different cases separately, to ensure that slow performance in
189
209
  # one method or code path isn't hidden by fast performance in another.
190
- benchmark_lambdas.map do |benchmark|
210
+ benchmark_jsons = benchmark_lambdas.map do |benchmark|
191
211
  json_file = Tempfile.new
192
212
 
193
213
  Benchmark.ips do |x|
@@ -203,42 +223,52 @@ benchmark_lambdas.map do |benchmark|
203
223
  end
204
224
 
205
225
  JSON.parse(json_file.read)
206
- end.each_with_index do |benchmark_json, i|
207
- # We print a comparison table after we run each benchmark to copy into our
208
- # README.md
209
-
210
- # MemoWise will not appear in the comparison table, but we will use it to
211
- # compare against other gems' benchmarks
212
- memo_wise = benchmark_json.find { _1["name"].include?("MemoWise") }
213
- benchmark_json.delete(memo_wise)
214
-
215
- # Sort benchmarks by gem name to alphabetize our final output table.
216
- benchmark_json.sort_by! { _1["name"] }
217
-
218
- # Print headers based on the first benchmark_json
219
- if i.zero?
220
- benchmark_headers = benchmark_json.map do |benchmark_gem|
221
- # Gem name is of the form:
222
- # "MemoWise (1.1.0): ()"
223
- # We use this mapping to get a header of the form
224
- # "`MemoWise` (1.1.0)
225
- gem_name_parts = benchmark_gem["name"].split
226
- "`#{gem_name_parts[0]}` #{gem_name_parts[1][...-1]}"
226
+ end
227
+
228
+ [true, false].each do |github_comparison|
229
+ benchmark_jsons.each_with_index do |benchmark_json, i|
230
+ # We print a comparison table after we run each benchmark to copy into our
231
+ # README.md
232
+
233
+ # MemoWise will not appear in the comparison table, but we will use it to
234
+ # compare against other gems' benchmarks
235
+ memo_wise = benchmark_json.find { |json| json["name"].split.first == "MemoWise" }
236
+ benchmark_json -= [memo_wise]
237
+
238
+ github_main = benchmark_json.find { |json| json["name"].split.first == GITHUB_MAIN }
239
+ benchmark_json = github_comparison ? [github_main] : benchmark_json - [github_main]
240
+
241
+ # Sort benchmarks by gem name to alphabetize our final output table.
242
+ benchmark_json.sort_by! { |json| json["name"] }
243
+
244
+ # Print headers based on the first benchmark_json
245
+ if i.zero?
246
+ benchmark_headers = benchmark_json.map do |benchmark_gem|
247
+ # Gem name is of the form:
248
+ # "MemoWise (1.1.0): ()"
249
+ # We use this mapping to get a header of the form
250
+ # "`MemoWise` (1.1.0)
251
+ gem_name_parts = benchmark_gem["name"].split
252
+ "`#{gem_name_parts[0]}` #{gem_name_parts[1][...-1]}"
253
+ end.join("|")
254
+ puts "|Method arguments|#{benchmark_headers}|"
255
+ puts "#{'|--' * (benchmark_json.size + 1)}|"
256
+ end
257
+
258
+ output_str = benchmark_json.map do |bgem|
259
+ # "%.2f" % 12.345 => "12.34" (instead of "12.35")
260
+ # See: https://bugs.ruby-lang.org/issues/12548
261
+ # 1.00.round(2).to_s => "1.0" (instead of "1.00")
262
+ #
263
+ # So to round and format correctly, we first use Float#round and then %
264
+ "%.#{N_RESULT_DECIMAL_DIGITS}fx" %
265
+ (memo_wise["central_tendency"] / bgem["central_tendency"]).round(N_RESULT_DECIMAL_DIGITS)
227
266
  end.join("|")
228
- puts "|Method arguments|#{benchmark_headers}|"
229
- puts "#{'|--' * (benchmark_json.size + 1)}|"
267
+
268
+ name = memo_wise["name"].partition(": ").last
269
+ puts "|`#{name}`#{' (none)' if name == '()'}|#{output_str}|"
230
270
  end
231
271
 
232
- output_str = benchmark_json.map do |bgem|
233
- # "%.2f" % 12.345 => "12.34" (instead of "12.35")
234
- # See: https://bugs.ruby-lang.org/issues/12548
235
- # 1.00.round(2).to_s => "1.0" (instead of "1.00")
236
- #
237
- # So to round and format correctly, we first use Float#round and then %
238
- "%.#{N_RESULT_DECIMAL_DIGITS}fx" %
239
- (memo_wise["central_tendency"] / bgem["central_tendency"]).round(N_RESULT_DECIMAL_DIGITS)
240
- end.join("|")
241
-
242
- name = memo_wise["name"].partition(": ").last
243
- puts "|`#{name}`#{' (none)' if name == '()'}|#{output_str}|"
272
+ # Output a blank line between sections
273
+ puts ""
244
274
  end
@@ -17,8 +17,8 @@ module MemoWise
17
17
  # zero_arg_method_name: :memoized_result,
18
18
  # single_arg_method_name: { arg1 => :memoized_result, ... },
19
19
  #
20
- # # Surprisingly, this is faster than a single top-level hash key of: [:multi_arg_method_name, arg1, arg2]
21
- # multi_arg_method_name: { [arg1, arg2] => :memoized_result, ... }
20
+ # # This is faster than a single top-level hash key of: [:multi_arg_method_name, arg1, arg2]
21
+ # multi_arg_method_name: { arg1 => { arg2 => :memoized_result, ... }, ... }
22
22
  # }
23
23
  obj.instance_variable_set(:@_memo_wise, {}) unless obj.instance_variable_defined?(:@_memo_wise)
24
24
 
@@ -70,7 +70,6 @@ module MemoWise
70
70
  case method_arguments(method)
71
71
  when SPLAT then "*args"
72
72
  when DOUBLE_SPLAT then "**kwargs"
73
- when SPLAT_AND_DOUBLE_SPLAT then "*args, **kwargs"
74
73
  when ONE_REQUIRED_POSITIONAL, ONE_REQUIRED_KEYWORD, MULTIPLE_REQUIRED
75
74
  method.parameters.map do |type, name|
76
75
  "#{name}#{':' if type == :keyreq}"
@@ -105,8 +104,6 @@ module MemoWise
105
104
  case method_arguments(method)
106
105
  when SPLAT then "args"
107
106
  when DOUBLE_SPLAT then "kwargs"
108
- when SPLAT_AND_DOUBLE_SPLAT then "[args, kwargs]"
109
- when MULTIPLE_REQUIRED then "[#{method.parameters.map(&:last).join(', ')}]"
110
107
  else
111
108
  raise ArgumentError, "Unexpected arguments for #{method.name}"
112
109
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MemoWise
4
- VERSION = "1.6.0"
4
+ VERSION = "1.7.0"
5
5
  end
data/lib/memo_wise.rb CHANGED
@@ -195,9 +195,40 @@ module MemoWise
195
195
  end
196
196
  end
197
197
  HEREDOC
198
- # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT,
199
- # MemoWise::InternalAPI::DOUBLE_SPLAT, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
200
- else
198
+ when MemoWise::InternalAPI::MULTIPLE_REQUIRED
199
+ # When we have multiple required params, we store the memoized values in a deeply nested hash, like:
200
+ # { method_name: { arg1 => { arg2 => { arg3 => memoized_value } } } }
201
+ last_index = method.parameters.size
202
+ layers = method.parameters.map.with_index(1) do |(_, name), index|
203
+ prev_hash = "_memo_wise_hash#{index - 1 if index > 1}"
204
+ fallback = if index == last_index
205
+ "#{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})"
206
+ else
207
+ "{}"
208
+ end
209
+ "_memo_wise_hash#{index} = #{prev_hash}.fetch(#{name}) { #{prev_hash}[#{name}] = #{fallback} }"
210
+ end
211
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
212
+ def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
213
+ _memo_wise_hash = (@_memo_wise[:#{method_name}] ||= {})
214
+ #{layers.join("\n ")}
215
+ end
216
+ HEREDOC
217
+ when MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
218
+ # When we have both *args and **kwargs, we store the memoized values in a deeply nested hash, like:
219
+ # { method_name: { args => { kwargs => memoized_value } } }
220
+ klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
221
+ def #{method_name}(*args, **kwargs)
222
+ _memo_wise_hash = (@_memo_wise[:#{method_name}] ||= {})
223
+ _memo_wise_kwargs_hash = _memo_wise_hash.fetch(args) do
224
+ _memo_wise_hash[args] = {}
225
+ end
226
+ _memo_wise_kwargs_hash.fetch(kwargs) do
227
+ _memo_wise_kwargs_hash[kwargs] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
228
+ end
229
+ end
230
+ HEREDOC
231
+ else # MemoWise::InternalAPI::SPLAT, MemoWise::InternalAPI::DOUBLE_SPLAT
201
232
  klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
202
233
  def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
203
234
  _memo_wise_hash = (@_memo_wise[:#{method_name}] ||= {})
@@ -430,12 +461,23 @@ module MemoWise
430
461
  when MemoWise::InternalAPI::SPLAT then hash[args] = yield
431
462
  when MemoWise::InternalAPI::DOUBLE_SPLAT then hash[kwargs] = yield
432
463
  when MemoWise::InternalAPI::MULTIPLE_REQUIRED
433
- key = method.parameters.map.with_index do |(type, name), idx|
434
- type == :req ? args[idx] : kwargs[name]
464
+ n_parameters = method.parameters.size
465
+ method.parameters.each_with_index do |(type, name), index|
466
+ val = type == :req ? args[index] : kwargs[name]
467
+
468
+ # Walk through the layers of nested hashes. When we get to the final
469
+ # layer, yield to the block to set its value.
470
+ if index < n_parameters - 1
471
+ hash = (hash[val] ||= {})
472
+ else
473
+ hash[val] = yield
474
+ end
435
475
  end
436
- hash[key] = yield
437
476
  else # MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
438
- hash[[args, kwargs]] = yield
477
+ # When we have both *args and **kwargs, we store the memoized values like:
478
+ # { method_name: { args => { kwargs => memoized_value } } }
479
+ # so we need to initialize `hash[args]`` if it does not already exist.
480
+ (hash[args] ||= {})[kwargs] = yield
439
481
  end
440
482
  end
441
483
 
@@ -530,15 +572,26 @@ module MemoWise
530
572
  when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD then method_hash&.delete(kwargs.first.last)
531
573
  when MemoWise::InternalAPI::SPLAT then method_hash&.delete(args)
532
574
  when MemoWise::InternalAPI::DOUBLE_SPLAT then method_hash&.delete(kwargs)
533
- else # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
534
- key = if method_arguments == MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
535
- [args, kwargs]
536
- else
537
- method.parameters.map.with_index do |(type, name), i|
538
- type == :req ? args[i] : kwargs[name]
539
- end
540
- end
541
- method_hash&.delete(key)
575
+ when MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
576
+ # Here, memoized values are stored like:
577
+ # { method_name: { args => { kwargs => memoized_value } } }
578
+ # so we need to delete the innermost value (because the same args array
579
+ # may have multiple memoized values for different kwargs hashes).
580
+ method_hash&.[](args)&.delete(kwargs)
581
+ else # MemoWise::InternalAPI::MULTIPLE_REQUIRED
582
+ n_parameters = method.parameters.size
583
+ method.parameters.each_with_index do |(type, name), index|
584
+ val = type == :req ? args[index] : kwargs[name]
585
+
586
+ # Walk through the layers of nested hashes. When we get to the final
587
+ # layer, delete its value. We use the safe navigation operator to
588
+ # gracefully handle any layer not yet existing.
589
+ if index < n_parameters - 1
590
+ method_hash = method_hash&.[](val)
591
+ else
592
+ method_hash&.delete(val)
593
+ end
594
+ end
542
595
  end
543
596
  end
544
597
  end
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.6.0
4
+ version: 1.7.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: 2022-01-24 00:00:00.000000000 Z
14
+ date: 2022-04-04 00:00:00.000000000 Z
15
15
  dependencies: []
16
16
  description:
17
17
  email:
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
70
  - !ruby/object:Gem::Version
71
71
  version: '0'
72
72
  requirements: []
73
- rubygems_version: 3.3.3
73
+ rubygems_version: 3.3.7
74
74
  signing_key:
75
75
  specification_version: 4
76
76
  summary: The wise choice for Ruby memoization