lib/memo_wise.rb in memo_wise-1.4.0 vs lib/memo_wise.rb in memo_wise-1.5.0

- old
+ new

@@ -178,139 +178,39 @@ method_arguments = MemoWise::InternalAPI.method_arguments(method) case method_arguments when MemoWise::InternalAPI::NONE - # Zero-arg methods can use simpler/more performant logic because the - # hash key is just the method name. - klass.send(:define_method, method_name) do # Ruby 2.4's `define_method` is private in some cases - index = MemoWise::InternalAPI.index(self, method_name) - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name} - if @_memo_wise_sentinels[#{index}] - @_memo_wise[#{index}] - else - ret = @_memo_wise[#{index}] = #{original_memo_wised_name} - @_memo_wise_sentinels[#{index}] = true - ret - end + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name} + @_memo_wise.fetch(:#{method_name}) do + @_memo_wise[:#{method_name}] = #{original_memo_wised_name} end - HEREDOC - - klass.send(visibility, method_name) - send(method_name) - end + end + HEREDOC when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL, MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD key = method.parameters.first.last - # NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use - # `define_method(...) do |*args, **kwargs|`. Instead we must use the - # simpler `|*args|` pattern. We can't just do this always though - # because Ruby 2.7 and above require `|*args, **kwargs|` to work - # correctly. - # See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26 - # :nocov: - if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby" - klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases - index = MemoWise::InternalAPI.index(self, method_name) - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) - _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) - _memo_wise_output = _memo_wise_hash[#{key}] - if _memo_wise_output || _memo_wise_hash.key?(#{key}) - _memo_wise_output - else - _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) - end - end - HEREDOC - - klass.send(visibility, method_name) - send(method_name, *args) + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) + _memo_wise_hash = (@_memo_wise[:#{method_name}] ||= {}) + _memo_wise_hash.fetch(#{key}) do + _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) + end end - # :nocov: - else - klass.define_method(method_name) do |*args, **kwargs| - index = MemoWise::InternalAPI.index(self, method_name) - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) - _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) - _memo_wise_output = _memo_wise_hash[#{key}] - if _memo_wise_output || _memo_wise_hash.key?(#{key}) - _memo_wise_output - else - _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) - end - end - HEREDOC - - klass.send(visibility, method_name) - send(method_name, *args, **kwargs) - end - end + HEREDOC # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT, # MemoWise::InternalAPI::DOUBLE_SPLAT, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT else - # NOTE: When benchmarking this implementation against something like: - # - # @_memo_wise.fetch(key) do - # ... - # end - # - # this implementation may sometimes perform worse than the above. This - # is because this case uses a more complex hash key (see - # `MemoWise::InternalAPI.key_str`), and hashing that key has less - # consistent performance. In general, this should still be faster for - # truthy results because `Hash#[]` generally performs hash lookups - # faster than `Hash#fetch`. - # - # NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use - # `define_method(...) do |*args, **kwargs|`. Instead we must use the - # simpler `|*args|` pattern. We can't just do this always though - # because Ruby 2.7 and above require `|*args, **kwargs|` to work - # correctly. - # See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26 - # :nocov: - if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby" - klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases - index = MemoWise::InternalAPI.index(self, method_name) - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) - _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) - _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)} - _memo_wise_output = _memo_wise_hash[_memo_wise_key] - if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key) - _memo_wise_output - else - _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) - end - end - HEREDOC - - klass.send(visibility, method_name) - send(method_name, *args) + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) + _memo_wise_hash = (@_memo_wise[:#{method_name}] ||= {}) + _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)} + _memo_wise_hash.fetch(_memo_wise_key) do + _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) + end end - # :nocov: - else # Ruby 2.7 and above break with (*args) - klass.define_method(method_name) do |*args, **kwargs| - index = MemoWise::InternalAPI.index(self, method_name) - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) - _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) - _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)} - _memo_wise_output = _memo_wise_hash[_memo_wise_key] - if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key) - _memo_wise_output - else - _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) - end - end - HEREDOC - - klass.send(visibility, method_name) - send(method_name, *args, **kwargs) - end - end + HEREDOC end klass.send(visibility, method_name) end end @@ -509,25 +409,24 @@ # ex.method_to_preset #=> "B" # # ex.method_called_times #=> nil # def preset_memo_wise(method_name, *args, **kwargs) + raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol) raise ArgumentError, "Pass a block as the value to preset for #{method_name}, #{args}" unless block_given? MemoWise::InternalAPI.validate_memo_wised!(self, method_name) method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name)) method_arguments = MemoWise::InternalAPI.method_arguments(method) - index = MemoWise::InternalAPI.index(self, method_name) if method_arguments == MemoWise::InternalAPI::NONE - @_memo_wise_sentinels[index] = true - @_memo_wise[index] = yield + @_memo_wise[method_name] = yield return end - hash = (@_memo_wise[index] ||= {}) + hash = (@_memo_wise[method_name] ||= {}) case method_arguments when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL then hash[args.first] = yield when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD then hash[kwargs.first.last] = yield when MemoWise::InternalAPI::SPLAT then hash[args] = yield @@ -611,62 +510,37 @@ if method_name.nil? raise ArgumentError, "Provided args when method_name = nil" unless args.empty? raise ArgumentError, "Provided kwargs when method_name = nil" unless kwargs.empty? @_memo_wise.clear - @_memo_wise_sentinels.clear return end raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol) raise ArgumentError, "#{method_name} is not a defined method" unless respond_to?(method_name, true) MemoWise::InternalAPI.validate_memo_wised!(self, method_name) method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name)) method_arguments = MemoWise::InternalAPI.method_arguments(method) - index = MemoWise::InternalAPI.index(self, method_name) + # method_name == MemoWise::InternalAPI::NONE will be covered by this case. + @_memo_wise.delete(method_name) if args.empty? && kwargs.empty? + method_hash = @_memo_wise[method_name] + case method_arguments - when MemoWise::InternalAPI::NONE - @_memo_wise_sentinels[index] = nil - @_memo_wise[index] = nil - when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL - if args.empty? - @_memo_wise[index]&.clear - else - @_memo_wise[index]&.delete(args.first) - end - when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD - if kwargs.empty? - @_memo_wise[index]&.clear - else - @_memo_wise[index]&.delete(kwargs.first.last) - end - when MemoWise::InternalAPI::SPLAT - if args.empty? - @_memo_wise[index]&.clear - else - @_memo_wise[index]&.delete(args) - end - when MemoWise::InternalAPI::DOUBLE_SPLAT - if kwargs.empty? - @_memo_wise[index]&.clear - else - @_memo_wise[index]&.delete(kwargs) - end + when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL then method_hash&.delete(args.first) + when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD then method_hash&.delete(kwargs.first.last) + when MemoWise::InternalAPI::SPLAT then method_hash&.delete(args) + when MemoWise::InternalAPI::DOUBLE_SPLAT then method_hash&.delete(kwargs) else # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT - if args.empty? && kwargs.empty? - @_memo_wise[index]&.clear - else - key = if method_arguments == MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT - [args, kwargs] - else - method.parameters.map.with_index do |(type, name), i| - type == :req ? args[i] : kwargs[name] # rubocop:disable Metrics/BlockNesting - end + key = if method_arguments == MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT + [args, kwargs] + else + method.parameters.map.with_index do |(type, name), i| + type == :req ? args[i] : kwargs[name] end - @_memo_wise[index]&.delete(key) - end + end + method_hash&.delete(key) end end end