Skip to content

Commit

Permalink
Implements memoization using instance variables per method
Browse files Browse the repository at this point in the history
Resolves #243
  • Loading branch information
jemmaissroff committed Dec 9, 2021
1 parent a97cd40 commit 4c549d4
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 18 deletions.
40 changes: 22 additions & 18 deletions lib/memo_wise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,10 @@ def inherited(subclass)
klass.send(:private, original_memo_wised_name)

method_arguments = MemoWise::InternalAPI.method_arguments(method)
index = MemoWise::InternalAPI.next_index!(klass, method_name)

case method_arguments
when MemoWise::InternalAPI::NONE
index = MemoWise::InternalAPI.next_index!(klass, method_name)
# Zero-arg methods can use simpler/more performant logic because the
# hash key is just the method name.
klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
Expand All @@ -200,7 +200,7 @@ def #{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_hash = (#{MemoWise::InternalAPI.method_name_to_sym(klass, method_name)} ||= {})
_memo_wise_output = _memo_wise_hash[#{key}]
if _memo_wise_output || _memo_wise_hash.key?(#{key})
_memo_wise_output
Expand All @@ -226,7 +226,7 @@ def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
# faster than `Hash#fetch`.
klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1
def #{method_name}(#{MemoWise::InternalAPI.args_str(method)})
_memo_wise_hash = (@_memo_wise[#{index}] ||= {})
_memo_wise_hash = (#{MemoWise::InternalAPI.method_name_to_sym(klass, method_name)} ||= {})
_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)
Expand Down Expand Up @@ -445,15 +445,16 @@ def preset_memo_wise(method_name, *args, **kwargs)

method = method(method_name)
method_arguments = MemoWise::InternalAPI.method_arguments(method)
index = api.index(method_name)

if method_arguments == MemoWise::InternalAPI::NONE
index = api.index(method_name)

@_memo_wise_sentinels[index] = true
@_memo_wise[index] = yield
return
end

hash = (@_memo_wise[index] ||= {})
hash = MemoWise::InternalAPI.memo_wise_hash(self, method_name)

case method_arguments
when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL then hash[args.first] = yield
Expand Down Expand Up @@ -540,8 +541,10 @@ def reset_memo_wise(method_name = nil, *args, **kwargs)
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
# Clear any instance variables created by memo_wise
instance_variables.select do |ivar|
ivar.to_s.start_with?("@_memo_wise")
end.map { |ivar| eval("#{ivar}.clear") }
return
end

Expand All @@ -553,39 +556,40 @@ def reset_memo_wise(method_name = nil, *args, **kwargs)

method = method(method_name)
method_arguments = MemoWise::InternalAPI.method_arguments(method)
index = api.index(method_name)
memo_wise_hash = MemoWise::InternalAPI.memo_wise_hash(self, method_name)

case method_arguments
when MemoWise::InternalAPI::NONE
index = api.index(method_name)
@_memo_wise_sentinels[index] = nil
@_memo_wise[index] = nil
when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL
if args.empty?
@_memo_wise[index]&.clear
memo_wise_hash&.clear
else
@_memo_wise[index]&.delete(args.first)
memo_wise_hash&.delete(args.first)
end
when MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD
if kwargs.empty?
@_memo_wise[index]&.clear
memo_wise_hash&.clear
else
@_memo_wise[index]&.delete(kwargs.first.last)
memo_wise_hash&.delete(kwargs.first.last)
end
when MemoWise::InternalAPI::SPLAT
if args.empty?
@_memo_wise[index]&.clear
memo_wise_hash&.clear
else
@_memo_wise[index]&.delete(args)
memo_wise_hash&.delete(args)
end
when MemoWise::InternalAPI::DOUBLE_SPLAT
if kwargs.empty?
@_memo_wise[index]&.clear
memo_wise_hash&.clear
else
@_memo_wise[index]&.delete(kwargs)
memo_wise_hash&.delete(kwargs)
end
else # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
if args.empty? && kwargs.empty?
@_memo_wise[index]&.clear
memo_wise_hash&.clear
else
key = if method_arguments == MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT
[args, kwargs]
Expand All @@ -594,7 +598,7 @@ def reset_memo_wise(method_name = nil, *args, **kwargs)
type == :req ? args[i] : kwargs[name] # rubocop:disable Metrics/BlockNesting
end
end
@_memo_wise[index]&.delete(key)
memo_wise_hash&.delete(key)
end
end
end
Expand Down
20 changes: 20 additions & 0 deletions lib/memo_wise/internal_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,26 @@ def validate_memo_wised!(method_name)
end
end

# Returns the hash that stores the memoized values for any method which
# takes arguments.
#
# @param klass [Class]
# Original class on which a method is being memoized
#
# @param method_name [Symbol]
# The name of the method being memoized
#
# @return [Hash]
# The hash which stores memoized values for method_name on klass
def self.memo_wise_hash(klass, method_name)
klass.instance_variable_get(method_name_to_sym(klass, method_name)) ||
klass.instance_variable_set(method_name_to_sym(klass, method_name), {})
end

def self.method_name_to_sym(klass, method_name)
"@_memo_wise_#{method_name}".gsub("?", "__q__").to_sym
end

private

# @return [Class] where we look for method definitions
Expand Down

0 comments on commit 4c549d4

Please sign in to comment.