memo_wise 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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