memo_wise 1.4.0 → 1.5.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/CHANGELOG.md +9 -2
- data/Gemfile.lock +1 -1
- data/README.md +19 -18
- data/benchmarks/benchmarks.rb +27 -84
- data/lib/memo_wise/internal_api.rb +4 -75
- data/lib/memo_wise/version.rb +1 -1
- data/lib/memo_wise.rb +39 -165
- metadata +3 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9d94c6c36ed049177dffb78f2bc5b0196d06e7b334643770163b7140ee2e77e2
         | 
| 4 | 
            +
              data.tar.gz: 2be9bd8578a7357aabe70d9f748cb57fedf50708028ca7c3ed55815814b7ddc2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 848080fdd9596f0a0f477464ddbc2009ab2c5f3caec56d3fa3c97dd275aca7687c94793af8124a7dd567a1c33abcdf3474cb2e13ab6b8b91f47302a0059b6916
         | 
| 7 | 
            +
              data.tar.gz: e79de4347bbc5cd0701095574dfb7faafb320dc3f6e1de1cb0978ebd1fcdce5e65ca0c8fa2e0079d72385acabea582caac41a0c6c553078f69d1337006422e0e
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -5,10 +5,16 @@ All notable changes to this project will be documented in this file. | |
| 5 5 | 
             
            The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
         | 
| 6 6 | 
             
            and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
         | 
| 7 7 |  | 
| 8 | 
            -
            ##  | 
| 8 | 
            +
            ## Unreleased
         | 
| 9 9 |  | 
| 10 10 | 
             
            - Nothing yet!
         | 
| 11 11 |  | 
| 12 | 
            +
            ## [1.5.0] - 2021-12-17
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            - Remove optimization for truthy results to fix race condition bugs
         | 
| 15 | 
            +
            - Switch to a simpler internal data structure to fix several classes of bugs
         | 
| 16 | 
            +
              that the previous few versions were unable to sufficiently address
         | 
| 17 | 
            +
             | 
| 12 18 | 
             
            ## [1.4.0] - 2021-12-10
         | 
| 13 19 |  | 
| 14 20 | 
             
            ### Fixed
         | 
| @@ -110,7 +116,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |
| 110 116 | 
             
            - Panolint
         | 
| 111 117 | 
             
            - Dependabot setup
         | 
| 112 118 |  | 
| 113 | 
            -
            [Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1. | 
| 119 | 
            +
            [Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1.5.0...HEAD
         | 
| 120 | 
            +
            [1.5.0]: https://github.com/panorama-ed/memo_wise/compare/v1.4.0...v1.5.0
         | 
| 114 121 | 
             
            [1.4.0]: https://github.com/panorama-ed/memo_wise/compare/v1.3.0...v1.4.0
         | 
| 115 122 | 
             
            [1.3.0]: https://github.com/panorama-ed/memo_wise/compare/v1.2.0...v1.3.0
         | 
| 116 123 | 
             
            [1.2.0]: https://github.com/panorama-ed/memo_wise/compare/v1.1.0...v1.2.0
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -21,6 +21,7 @@ | |
| 21 21 | 
             
              * Support for memoization on frozen objects
         | 
| 22 22 | 
             
              * Support for memoization of class and module methods
         | 
| 23 23 | 
             
              * Support for inheritance of memoized class and instance methods
         | 
| 24 | 
            +
              * Documented and tested [thread-safety guarantees](#thread-safety)
         | 
| 24 25 | 
             
              * Full [documentation](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise) and [test coverage](https://codecov.io/gh/panorama-ed/memo_wise)!
         | 
| 25 26 |  | 
| 26 27 | 
             
            ## Installation
         | 
| @@ -118,15 +119,15 @@ Results using Ruby 3.0.3: | |
| 118 119 |  | 
| 119 120 | 
             
            |Method arguments|`Dry::Core`\* (0.7.1)|`Memery` (1.4.0)|
         | 
| 120 121 | 
             
            |--|--|--|
         | 
| 121 | 
            -
            |`()` (none)|1. | 
| 122 | 
            -
            |`(a)`| | 
| 123 | 
            -
            |`(a, b)`|0. | 
| 124 | 
            -
            |`(a:)`| | 
| 125 | 
            -
            |`(a:, b:)`|0. | 
| 126 | 
            -
            |`(a, b:)`|0. | 
| 127 | 
            -
            |`(a, *args)`|0.83x| | 
| 128 | 
            -
            |`(a:, **kwargs)`|0. | 
| 129 | 
            -
            |`(a, *args, b:, **kwargs)`|0. | 
| 122 | 
            +
            |`()` (none)|1.06x|11.96x|
         | 
| 123 | 
            +
            |`(a)`|1.99x|10.19x|
         | 
| 124 | 
            +
            |`(a, b)`|0.39x|1.94x|
         | 
| 125 | 
            +
            |`(a:)`|1.80x|19.36x|
         | 
| 126 | 
            +
            |`(a:, b:)`|0.43x|4.26x|
         | 
| 127 | 
            +
            |`(a, b:)`|0.39x|3.97x|
         | 
| 128 | 
            +
            |`(a, *args)`|0.83x|1.85x|
         | 
| 129 | 
            +
            |`(a:, **kwargs)`|0.77x|2.94x|
         | 
| 130 | 
            +
            |`(a, *args, b:, **kwargs)`|0.59x|1.57x|
         | 
| 130 131 |  | 
| 131 132 | 
             
            \* `Dry::Core`
         | 
| 132 133 | 
             
            [may cause incorrect behavior caused by hash collisions](https://github.com/dry-rb/dry-core/issues/63).
         | 
| @@ -135,15 +136,15 @@ Results using Ruby 2.7.5 (because these gems raise errors in Ruby 3.x): | |
| 135 136 |  | 
| 136 137 | 
             
            |Method arguments|`DDMemoize` (1.0.0)|`Memoist` (0.16.2)|`Memoized` (1.0.2)|`Memoizer` (1.0.3)|
         | 
| 137 138 | 
             
            |--|--|--|--|--|
         | 
| 138 | 
            -
            |`()` (none)| | 
| 139 | 
            -
            |`(a)`| | 
| 140 | 
            -
            |`(a, b)`|3. | 
| 141 | 
            -
            |`(a:)`| | 
| 142 | 
            -
            |`(a:, b:)`|5.25x|4. | 
| 143 | 
            -
            |`(a, b:)`| | 
| 144 | 
            -
            |`(a, *args)`|3. | 
| 145 | 
            -
            |`(a:, **kwargs)`|2.87x|2. | 
| 146 | 
            -
            |`(a, *args, b:, **kwargs)`|2. | 
| 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|
         | 
| 147 148 |  | 
| 148 149 | 
             
            You can run benchmarks yourself with:
         | 
| 149 150 |  | 
    
        data/benchmarks/benchmarks.rb
    CHANGED
    
    | @@ -64,10 +64,6 @@ BENCHMARK_GEMS = [ | |
| 64 64 | 
             
            # the same way.
         | 
| 65 65 | 
             
            BENCHMARK_GEMS.each do |benchmark_gem|
         | 
| 66 66 | 
             
              eval <<~HEREDOC, binding, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
         | 
| 67 | 
            -
                # For these methods, we alternately return truthy and falsey values in
         | 
| 68 | 
            -
                # order to benchmark memoization when the result of a method is falsey.
         | 
| 69 | 
            -
                #
         | 
| 70 | 
            -
                # We do this by checking if the first argument to a method is even.
         | 
| 71 67 | 
             
                class #{benchmark_gem.klass}Example
         | 
| 72 68 | 
             
                  #{benchmark_gem.activation_code}
         | 
| 73 69 |  | 
| @@ -76,168 +72,115 @@ BENCHMARK_GEMS.each do |benchmark_gem| | |
| 76 72 | 
             
                  end
         | 
| 77 73 | 
             
                  #{benchmark_gem.memoization_method} :no_args
         | 
| 78 74 |  | 
| 79 | 
            -
                  # For the no_args case, we can't depend on arguments to alternate between
         | 
| 80 | 
            -
                  # returning truthy and falsey values, so instead make two separate
         | 
| 81 | 
            -
                  # no_args methods
         | 
| 82 | 
            -
                  def no_args_falsey
         | 
| 83 | 
            -
                    nil
         | 
| 84 | 
            -
                  end
         | 
| 85 | 
            -
                  #{benchmark_gem.memoization_method} :no_args_falsey
         | 
| 86 | 
            -
             | 
| 87 75 | 
             
                  def one_positional_arg(a)
         | 
| 88 | 
            -
                    100 | 
| 76 | 
            +
                    100
         | 
| 89 77 | 
             
                  end
         | 
| 90 78 | 
             
                  #{benchmark_gem.memoization_method} :one_positional_arg
         | 
| 91 79 |  | 
| 92 80 | 
             
                  def positional_args(a, b)
         | 
| 93 | 
            -
                    100 | 
| 81 | 
            +
                    100
         | 
| 94 82 | 
             
                  end
         | 
| 95 83 | 
             
                  #{benchmark_gem.memoization_method} :positional_args
         | 
| 96 84 |  | 
| 97 85 | 
             
                  def one_keyword_arg(a:)
         | 
| 98 | 
            -
                    100 | 
| 86 | 
            +
                    100
         | 
| 99 87 | 
             
                  end
         | 
| 100 88 | 
             
                  #{benchmark_gem.memoization_method} :one_keyword_arg
         | 
| 101 89 |  | 
| 102 90 | 
             
                  def keyword_args(a:, b:)
         | 
| 103 | 
            -
                    100 | 
| 91 | 
            +
                    100
         | 
| 104 92 | 
             
                  end
         | 
| 105 93 | 
             
                  #{benchmark_gem.memoization_method} :keyword_args
         | 
| 106 94 |  | 
| 107 95 | 
             
                  def positional_and_keyword_args(a, b:)
         | 
| 108 | 
            -
                    100 | 
| 96 | 
            +
                    100
         | 
| 109 97 | 
             
                  end
         | 
| 110 98 | 
             
                  #{benchmark_gem.memoization_method} :positional_and_keyword_args
         | 
| 111 99 |  | 
| 112 100 | 
             
                  def positional_and_splat_args(a, *args)
         | 
| 113 | 
            -
                    100 | 
| 101 | 
            +
                    100
         | 
| 114 102 | 
             
                  end
         | 
| 115 103 | 
             
                  #{benchmark_gem.memoization_method} :positional_and_splat_args
         | 
| 116 104 |  | 
| 117 105 | 
             
                  def keyword_and_double_splat_args(a:, **kwargs)
         | 
| 118 | 
            -
                    100 | 
| 106 | 
            +
                    100
         | 
| 119 107 | 
             
                  end
         | 
| 120 108 | 
             
                  #{benchmark_gem.memoization_method} :keyword_and_double_splat_args
         | 
| 121 109 |  | 
| 122 110 | 
             
                  def positional_splat_keyword_and_double_splat_args(a, *args, b:, **kwargs)
         | 
| 123 | 
            -
                    100 | 
| 111 | 
            +
                    100
         | 
| 124 112 | 
             
                  end
         | 
| 125 113 | 
             
                  #{benchmark_gem.memoization_method} :positional_splat_keyword_and_double_splat_args
         | 
| 126 114 | 
             
                end
         | 
| 127 115 | 
             
              HEREDOC
         | 
| 128 116 | 
             
            end
         | 
| 129 117 |  | 
| 130 | 
            -
            # We pre-create argument lists for our memoized methods with arguments, so that
         | 
| 131 | 
            -
            # our benchmarks are running the exact same inputs for each case.
         | 
| 132 | 
            -
            #
         | 
| 133 | 
            -
            # NOTE: The proportion of falsey results is 1/N_UNIQUE_ARGUMENTS (because for
         | 
| 134 | 
            -
            # the methods with arguments we are truthy for all but the first unique argument
         | 
| 135 | 
            -
            # set, and for zero-arity methods we manually execute `no_args` N_TRUTHY_RESULTS
         | 
| 136 | 
            -
            # times per each execution of `no_args_falsey`). This number was selected as the
         | 
| 137 | 
            -
            # lowest number such that this logic:
         | 
| 138 | 
            -
            #
         | 
| 139 | 
            -
            #   output = hash[key]
         | 
| 140 | 
            -
            #   if output || hash.key?(key)
         | 
| 141 | 
            -
            #     output
         | 
| 142 | 
            -
            #   else
         | 
| 143 | 
            -
            #     hash[key] = _original_method(...)
         | 
| 144 | 
            -
            #   end
         | 
| 145 | 
            -
            #
         | 
| 146 | 
            -
            # is consistently faster for cached lookups than:
         | 
| 147 | 
            -
            #
         | 
| 148 | 
            -
            #   hash.fetch(key) do
         | 
| 149 | 
            -
            #     hash[key] = _original_method(...)
         | 
| 150 | 
            -
            #   end
         | 
| 151 | 
            -
            #
         | 
| 152 | 
            -
            # as a result of `Hash#[]` having less overhead than `Hash#fetch`.
         | 
| 153 | 
            -
            #
         | 
| 154 | 
            -
            # We believe this is a reasonable choice because we believe most memoized method
         | 
| 155 | 
            -
            # results will be truthy, and so that is the case we should most optimize for.
         | 
| 156 | 
            -
            # However, we do not want to completely remove falsey method results from these
         | 
| 157 | 
            -
            # benchmarks because we do want to catch performance regressions for that case,
         | 
| 158 | 
            -
            # since it has its own "hot path."
         | 
| 159 | 
            -
            N_UNIQUE_ARGUMENTS = 30
         | 
| 160 | 
            -
            ARGUMENTS = Array.new(N_UNIQUE_ARGUMENTS) { |i| [i, i + 1] }
         | 
| 161 | 
            -
            N_TRUTHY_RESULTS = N_UNIQUE_ARGUMENTS - 1
         | 
| 162 118 | 
             
            N_RESULT_DECIMAL_DIGITS = 2
         | 
| 163 119 |  | 
| 164 120 | 
             
            # Each method within these benchmarks is initially run once to memoize the
         | 
| 165 121 | 
             
            # result value, so our benchmark only tests memoized retrieval time.
         | 
| 166 122 | 
             
            benchmark_lambdas = [
         | 
| 167 123 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 168 | 
            -
                instance.no_args_falsey
         | 
| 169 124 | 
             
                instance.no_args
         | 
| 170 125 |  | 
| 171 126 | 
             
                x.report("#{benchmark_gem.benchmark_name}: ()") do
         | 
| 172 | 
            -
                  instance. | 
| 173 | 
            -
                  N_TRUTHY_RESULTS.times { instance.no_args }
         | 
| 127 | 
            +
                  instance.no_args
         | 
| 174 128 | 
             
                end
         | 
| 175 129 | 
             
              end,
         | 
| 176 130 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 177 | 
            -
                 | 
| 131 | 
            +
                instance.one_positional_arg(1)
         | 
| 178 132 |  | 
| 179 133 | 
             
                x.report("#{benchmark_gem.benchmark_name}: (a)") do
         | 
| 180 | 
            -
                   | 
| 134 | 
            +
                  instance.one_positional_arg(1)
         | 
| 181 135 | 
             
                end
         | 
| 182 136 | 
             
              end,
         | 
| 183 137 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 184 | 
            -
                 | 
| 138 | 
            +
                instance.positional_args(1, 2)
         | 
| 185 139 |  | 
| 186 140 | 
             
                x.report("#{benchmark_gem.benchmark_name}: (a, b)") do
         | 
| 187 | 
            -
                   | 
| 141 | 
            +
                  instance.positional_args(1, 2)
         | 
| 188 142 | 
             
                end
         | 
| 189 143 | 
             
              end,
         | 
| 190 144 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 191 | 
            -
                 | 
| 145 | 
            +
                instance.one_keyword_arg(a: 1)
         | 
| 192 146 |  | 
| 193 147 | 
             
                x.report("#{benchmark_gem.benchmark_name}: (a:)") do
         | 
| 194 | 
            -
                   | 
| 148 | 
            +
                  instance.one_keyword_arg(a: 1)
         | 
| 195 149 | 
             
                end
         | 
| 196 150 | 
             
              end,
         | 
| 197 151 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 198 | 
            -
                 | 
| 152 | 
            +
                instance.keyword_args(a: 1, b: 2)
         | 
| 199 153 |  | 
| 200 154 | 
             
                x.report("#{benchmark_gem.benchmark_name}: (a:, b:)") do
         | 
| 201 | 
            -
                   | 
| 155 | 
            +
                  instance.keyword_args(a: 1, b: 2)
         | 
| 202 156 | 
             
                end
         | 
| 203 157 | 
             
              end,
         | 
| 204 158 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 205 | 
            -
                 | 
| 159 | 
            +
                instance.positional_and_keyword_args(1, b: 2)
         | 
| 206 160 |  | 
| 207 161 | 
             
                x.report("#{benchmark_gem.benchmark_name}: (a, b:)") do
         | 
| 208 | 
            -
                   | 
| 162 | 
            +
                  instance.positional_and_keyword_args(1, b: 2)
         | 
| 209 163 | 
             
                end
         | 
| 210 164 | 
             
              end,
         | 
| 211 165 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 212 | 
            -
                 | 
| 166 | 
            +
                instance.positional_and_splat_args(1, 2)
         | 
| 213 167 |  | 
| 214 168 | 
             
                x.report("#{benchmark_gem.benchmark_name}: (a, *args)") do
         | 
| 215 | 
            -
                   | 
| 169 | 
            +
                  instance.positional_and_splat_args(1, 2)
         | 
| 216 170 | 
             
                end
         | 
| 217 171 | 
             
              end,
         | 
| 218 172 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 219 | 
            -
                 | 
| 173 | 
            +
                instance.keyword_and_double_splat_args(a: 1, b: 2)
         | 
| 220 174 |  | 
| 221 | 
            -
                x.report(
         | 
| 222 | 
            -
                   | 
| 223 | 
            -
                ) do
         | 
| 224 | 
            -
                  ARGUMENTS.each do |a, b|
         | 
| 225 | 
            -
                    instance.keyword_and_double_splat_args(a: a, b: b)
         | 
| 226 | 
            -
                  end
         | 
| 175 | 
            +
                x.report("#{benchmark_gem.benchmark_name}: (a:, **kwargs)") do
         | 
| 176 | 
            +
                  instance.keyword_and_double_splat_args(a: 1, b: 2)
         | 
| 227 177 | 
             
                end
         | 
| 228 178 | 
             
              end,
         | 
| 229 179 | 
             
              lambda do |x, instance, benchmark_gem|
         | 
| 230 | 
            -
                 | 
| 231 | 
            -
                  instance.positional_splat_keyword_and_double_splat_args(a, b, b: b, a: a)
         | 
| 232 | 
            -
                end
         | 
| 180 | 
            +
                instance.positional_splat_keyword_and_double_splat_args(1, 2, b: 3, a: 4)
         | 
| 233 181 |  | 
| 234 | 
            -
                x.report(
         | 
| 235 | 
            -
                   | 
| 236 | 
            -
                ) do
         | 
| 237 | 
            -
                  ARGUMENTS.each do |a, b|
         | 
| 238 | 
            -
                    instance.
         | 
| 239 | 
            -
                      positional_splat_keyword_and_double_splat_args(a, b, b: b, a: a)
         | 
| 240 | 
            -
                  end
         | 
| 182 | 
            +
                x.report("#{benchmark_gem.benchmark_name}: (a, *args, b:, **kwargs)") do
         | 
| 183 | 
            +
                  instance.positional_splat_keyword_and_double_splat_args(1, 2, b: 3, a: 4)
         | 
| 241 184 | 
             
                end
         | 
| 242 185 | 
             
              end
         | 
| 243 186 | 
             
            ]
         | 
| @@ -10,14 +10,9 @@ module MemoWise | |
| 10 10 | 
             
                #
         | 
| 11 11 | 
             
                # @return [Object] the passed-in obj
         | 
| 12 12 | 
             
                def self.create_memo_wise_state!(obj)
         | 
| 13 | 
            -
                  # `@_memo_wise` stores memoized results of method calls | 
| 14 | 
            -
                  # slightly different for different types of | 
| 15 | 
            -
                  # | 
| 16 | 
            -
                  #     :memoized_result, # For method 0 (which takes no arguments)
         | 
| 17 | 
            -
                  #     { arg1 => :memoized_result, ... }, # For method 1 (which takes an argument)
         | 
| 18 | 
            -
                  #     { [arg1, arg2] => :memoized_result, ... } # For method 2 (which takes multiple arguments)
         | 
| 19 | 
            -
                  #   ]
         | 
| 20 | 
            -
                  # This is a faster alternative to:
         | 
| 13 | 
            +
                  # `@_memo_wise` stores memoized results of method calls in a hash keyed on
         | 
| 14 | 
            +
                  # method name. The structure is slightly different for different types of
         | 
| 15 | 
            +
                  # methods. It looks like:
         | 
| 21 16 | 
             
                  #   {
         | 
| 22 17 | 
             
                  #     zero_arg_method_name: :memoized_result,
         | 
| 23 18 | 
             
                  #     single_arg_method_name: { arg1 => :memoized_result, ... },
         | 
| @@ -25,34 +20,7 @@ module MemoWise | |
| 25 20 | 
             
                  #     # Surprisingly, this is faster than a single top-level hash key of: [:multi_arg_method_name, arg1, arg2]
         | 
| 26 21 | 
             
                  #     multi_arg_method_name: { [arg1, arg2] => :memoized_result, ... }
         | 
| 27 22 | 
             
                  #   }
         | 
| 28 | 
            -
                   | 
| 29 | 
            -
                  # perform that array lookup more quickly than a hash lookup by method
         | 
| 30 | 
            -
                  # name.
         | 
| 31 | 
            -
                  obj.instance_variable_set(:@_memo_wise, []) unless obj.instance_variable_defined?(:@_memo_wise)
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                  # For zero-arity methods, memoized values are stored in the `@_memo_wise`
         | 
| 34 | 
            -
                  # array. Arrays do not differentiate between "unset" and "set to nil" and
         | 
| 35 | 
            -
                  # so to handle this case we need another array to store sentinels and
         | 
| 36 | 
            -
                  # store `true` at indexes for which a zero-arity method has been memoized.
         | 
| 37 | 
            -
                  # `@_memo_wise_sentinels` looks like:
         | 
| 38 | 
            -
                  #   [
         | 
| 39 | 
            -
                  #     true, # A zero-arity method's result has been memoized
         | 
| 40 | 
            -
                  #     nil, # A zero-arity method's result has not been memoized
         | 
| 41 | 
            -
                  #     nil, # A one-arity method will always correspond to `nil` here
         | 
| 42 | 
            -
                  #     ...
         | 
| 43 | 
            -
                  #   ]
         | 
| 44 | 
            -
                  # NOTE: Because `@_memo_wise` stores memoized values for more than just
         | 
| 45 | 
            -
                  # zero-arity methods, the `@_memo_wise_sentinels` array can end up being
         | 
| 46 | 
            -
                  # sparse (see above), even when all methods' memoized values have been
         | 
| 47 | 
            -
                  # stored. If this becomes an issue we could store a separate index for
         | 
| 48 | 
            -
                  # zero-arity methods to make every element in `@_memo_wise_sentinels`
         | 
| 49 | 
            -
                  # correspond to a zero-arity method.
         | 
| 50 | 
            -
                  # NOTE: Surprisingly, lookups on an array of `true` and `nil` values
         | 
| 51 | 
            -
                  # appear to outperform even bitwise operators on integers (as of Ruby
         | 
| 52 | 
            -
                  # 3.0.2), allowing us to avoid more complex sentinel structures.
         | 
| 53 | 
            -
                  unless obj.instance_variable_defined?(:@_memo_wise_sentinels)
         | 
| 54 | 
            -
                    obj.instance_variable_set(:@_memo_wise_sentinels, [])
         | 
| 55 | 
            -
                  end
         | 
| 23 | 
            +
                  obj.instance_variable_set(:@_memo_wise, {}) unless obj.instance_variable_defined?(:@_memo_wise)
         | 
| 56 24 |  | 
| 57 25 | 
             
                  obj
         | 
| 58 26 | 
             
                end
         | 
| @@ -188,15 +156,6 @@ module MemoWise | |
| 188 156 | 
             
                  :"_memo_wise_original_#{method_name}"
         | 
| 189 157 | 
             
                end
         | 
| 190 158 |  | 
| 191 | 
            -
                # @param method_name [Symbol] the name of the memoized method
         | 
| 192 | 
            -
                # @return [Integer] the array index in `@_memo_wise_indices` to use to find
         | 
| 193 | 
            -
                #   the memoization data for the given method
         | 
| 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)
         | 
| 198 | 
            -
                end
         | 
| 199 | 
            -
             | 
| 200 159 | 
             
                # Returns visibility of an instance method defined on class `target`.
         | 
| 201 160 | 
             
                #
         | 
| 202 161 | 
             
                # @param target [Class, Module]
         | 
| @@ -252,35 +211,5 @@ module MemoWise | |
| 252 211 | 
             
                  end
         | 
| 253 212 | 
             
                end
         | 
| 254 213 | 
             
                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!
         | 
| 285 214 | 
             
              end
         | 
| 286 215 | 
             
            end
         | 
    
        data/lib/memo_wise/version.rb
    CHANGED
    
    
    
        data/lib/memo_wise.rb
    CHANGED
    
    | @@ -180,135 +180,35 @@ module MemoWise | |
| 180 180 |  | 
| 181 181 | 
             
                    case method_arguments
         | 
| 182 182 | 
             
                    when MemoWise::InternalAPI::NONE
         | 
| 183 | 
            -
                       | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 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
         | 
| 183 | 
            +
                      klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
         | 
| 184 | 
            +
                        def #{method_name}
         | 
| 185 | 
            +
                          @_memo_wise.fetch(:#{method_name}) do
         | 
| 186 | 
            +
                            @_memo_wise[:#{method_name}] = #{original_memo_wised_name}
         | 
| 196 187 | 
             
                          end
         | 
| 197 | 
            -
                         | 
| 198 | 
            -
             | 
| 199 | 
            -
                        klass.send(visibility, method_name)
         | 
| 200 | 
            -
                        send(method_name)
         | 
| 201 | 
            -
                      end
         | 
| 188 | 
            +
                        end
         | 
| 189 | 
            +
                      HEREDOC
         | 
| 202 190 | 
             
                    when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL, MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD
         | 
| 203 191 | 
             
                      key = method.parameters.first.last
         | 
| 204 | 
            -
                       | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 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
         | 
| 225 | 
            -
             | 
| 226 | 
            -
                          klass.send(visibility, method_name)
         | 
| 227 | 
            -
                          send(method_name, *args)
         | 
| 228 | 
            -
                        end
         | 
| 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)
         | 
| 192 | 
            +
                      klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
         | 
| 193 | 
            +
                        def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
         | 
| 194 | 
            +
                          _memo_wise_hash = (@_memo_wise[:#{method_name}] ||= {})
         | 
| 195 | 
            +
                          _memo_wise_hash.fetch(#{key}) do
         | 
| 196 | 
            +
                            _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
         | 
| 197 | 
            +
                          end
         | 
| 247 198 | 
             
                        end
         | 
| 248 | 
            -
                       | 
| 199 | 
            +
                      HEREDOC
         | 
| 249 200 | 
             
                    # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT,
         | 
| 250 201 | 
             
                    # MemoWise::InternalAPI::DOUBLE_SPLAT, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
         | 
| 251 202 | 
             
                    else
         | 
| 252 | 
            -
                       | 
| 253 | 
            -
             | 
| 254 | 
            -
             | 
| 255 | 
            -
             | 
| 256 | 
            -
             | 
| 257 | 
            -
             | 
| 258 | 
            -
             | 
| 259 | 
            -
                      # is because this case uses a more complex hash key (see
         | 
| 260 | 
            -
                      # `MemoWise::InternalAPI.key_str`), and hashing that key has less
         | 
| 261 | 
            -
                      # consistent performance. In general, this should still be faster for
         | 
| 262 | 
            -
                      # truthy results because `Hash#[]` generally performs hash lookups
         | 
| 263 | 
            -
                      # faster than `Hash#fetch`.
         | 
| 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)
         | 
| 290 | 
            -
                        end
         | 
| 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)
         | 
| 203 | 
            +
                      klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
         | 
| 204 | 
            +
                        def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
         | 
| 205 | 
            +
                          _memo_wise_hash = (@_memo_wise[:#{method_name}] ||= {})
         | 
| 206 | 
            +
                          _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)}
         | 
| 207 | 
            +
                          _memo_wise_hash.fetch(_memo_wise_key) do
         | 
| 208 | 
            +
                            _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)})
         | 
| 209 | 
            +
                          end
         | 
| 310 210 | 
             
                        end
         | 
| 311 | 
            -
                       | 
| 211 | 
            +
                      HEREDOC
         | 
| 312 212 | 
             
                    end
         | 
| 313 213 |  | 
| 314 214 | 
             
                    klass.send(visibility, method_name)
         | 
| @@ -511,21 +411,20 @@ module MemoWise | |
| 511 411 | 
             
              #   ex.method_called_times #=> nil
         | 
| 512 412 | 
             
              #
         | 
| 513 413 | 
             
              def preset_memo_wise(method_name, *args, **kwargs)
         | 
| 414 | 
            +
                raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol)
         | 
| 514 415 | 
             
                raise ArgumentError, "Pass a block as the value to preset for #{method_name}, #{args}" unless block_given?
         | 
| 515 416 |  | 
| 516 417 | 
             
                MemoWise::InternalAPI.validate_memo_wised!(self, method_name)
         | 
| 517 418 |  | 
| 518 419 | 
             
                method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name))
         | 
| 519 420 | 
             
                method_arguments = MemoWise::InternalAPI.method_arguments(method)
         | 
| 520 | 
            -
                index = MemoWise::InternalAPI.index(self, method_name)
         | 
| 521 421 |  | 
| 522 422 | 
             
                if method_arguments == MemoWise::InternalAPI::NONE
         | 
| 523 | 
            -
                  @ | 
| 524 | 
            -
                  @_memo_wise[index] = yield
         | 
| 423 | 
            +
                  @_memo_wise[method_name] = yield
         | 
| 525 424 | 
             
                  return
         | 
| 526 425 | 
             
                end
         | 
| 527 426 |  | 
| 528 | 
            -
                hash = (@_memo_wise[ | 
| 427 | 
            +
                hash = (@_memo_wise[method_name] ||= {})
         | 
| 529 428 |  | 
| 530 429 | 
             
                case method_arguments
         | 
| 531 430 | 
             
                when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL then hash[args.first] = yield
         | 
| @@ -613,7 +512,6 @@ module MemoWise | |
| 613 512 | 
             
                  raise ArgumentError, "Provided kwargs when method_name = nil" unless kwargs.empty?
         | 
| 614 513 |  | 
| 615 514 | 
             
                  @_memo_wise.clear
         | 
| 616 | 
            -
                  @_memo_wise_sentinels.clear
         | 
| 617 515 | 
             
                  return
         | 
| 618 516 | 
             
                end
         | 
| 619 517 |  | 
| @@ -624,49 +522,25 @@ module MemoWise | |
| 624 522 |  | 
| 625 523 | 
             
                method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name))
         | 
| 626 524 | 
             
                method_arguments = MemoWise::InternalAPI.method_arguments(method)
         | 
| 627 | 
            -
             | 
| 525 | 
            +
             | 
| 526 | 
            +
                # method_name == MemoWise::InternalAPI::NONE will be covered by this case.
         | 
| 527 | 
            +
                @_memo_wise.delete(method_name) if args.empty? && kwargs.empty?
         | 
| 528 | 
            +
                method_hash = @_memo_wise[method_name]
         | 
| 628 529 |  | 
| 629 530 | 
             
                case method_arguments
         | 
| 630 | 
            -
                when MemoWise::InternalAPI:: | 
| 631 | 
            -
             | 
| 632 | 
            -
             | 
| 633 | 
            -
                when MemoWise::InternalAPI:: | 
| 634 | 
            -
                  if args.empty?
         | 
| 635 | 
            -
                    @_memo_wise[index]&.clear
         | 
| 636 | 
            -
                  else
         | 
| 637 | 
            -
                    @_memo_wise[index]&.delete(args.first)
         | 
| 638 | 
            -
                  end
         | 
| 639 | 
            -
                when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD
         | 
| 640 | 
            -
                  if kwargs.empty?
         | 
| 641 | 
            -
                    @_memo_wise[index]&.clear
         | 
| 642 | 
            -
                  else
         | 
| 643 | 
            -
                    @_memo_wise[index]&.delete(kwargs.first.last)
         | 
| 644 | 
            -
                  end
         | 
| 645 | 
            -
                when MemoWise::InternalAPI::SPLAT
         | 
| 646 | 
            -
                  if args.empty?
         | 
| 647 | 
            -
                    @_memo_wise[index]&.clear
         | 
| 648 | 
            -
                  else
         | 
| 649 | 
            -
                    @_memo_wise[index]&.delete(args)
         | 
| 650 | 
            -
                  end
         | 
| 651 | 
            -
                when MemoWise::InternalAPI::DOUBLE_SPLAT
         | 
| 652 | 
            -
                  if kwargs.empty?
         | 
| 653 | 
            -
                    @_memo_wise[index]&.clear
         | 
| 654 | 
            -
                  else
         | 
| 655 | 
            -
                    @_memo_wise[index]&.delete(kwargs)
         | 
| 656 | 
            -
                  end
         | 
| 531 | 
            +
                when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL then method_hash&.delete(args.first)
         | 
| 532 | 
            +
                when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD then method_hash&.delete(kwargs.first.last)
         | 
| 533 | 
            +
                when MemoWise::InternalAPI::SPLAT then method_hash&.delete(args)
         | 
| 534 | 
            +
                when MemoWise::InternalAPI::DOUBLE_SPLAT then method_hash&.delete(kwargs)
         | 
| 657 535 | 
             
                else # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
         | 
| 658 | 
            -
                  if  | 
| 659 | 
            -
             | 
| 660 | 
            -
             | 
| 661 | 
            -
             | 
| 662 | 
            -
                            [ | 
| 663 | 
            -
                          else
         | 
| 664 | 
            -
                            method.parameters.map.with_index do |(type, name), i|
         | 
| 665 | 
            -
                              type == :req ? args[i] : kwargs[name] # rubocop:disable Metrics/BlockNesting
         | 
| 666 | 
            -
                            end
         | 
| 536 | 
            +
                  key = if method_arguments == MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
         | 
| 537 | 
            +
                          [args, kwargs]
         | 
| 538 | 
            +
                        else
         | 
| 539 | 
            +
                          method.parameters.map.with_index do |(type, name), i|
         | 
| 540 | 
            +
                            type == :req ? args[i] : kwargs[name]
         | 
| 667 541 | 
             
                          end
         | 
| 668 | 
            -
             | 
| 669 | 
            -
                   | 
| 542 | 
            +
                        end
         | 
| 543 | 
            +
                  method_hash&.delete(key)
         | 
| 670 544 | 
             
                end
         | 
| 671 545 | 
             
              end
         | 
| 672 546 | 
             
            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. | 
| 4 | 
            +
              version: 1.5.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-12- | 
| 14 | 
            +
            date: 2021-12-20 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.2. | 
| 73 | 
            +
            rubygems_version: 3.2.22
         | 
| 74 74 | 
             
            signing_key:
         | 
| 75 75 | 
             
            specification_version: 4
         | 
| 76 76 | 
             
            summary: The wise choice for Ruby memoization
         |