Last active
December 22, 2021 14:11
-
-
Save leandro/29f844b0931adc4f8745214e2cbb39e7 to your computer and use it in GitHub Desktop.
A simple memoization module (concern)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass | |
include Memoization::Memoizable | |
memoize :my_memoizable_method | |
def my_memoizable_method | |
p 123 | |
:abc | |
end | |
end | |
a = MyClass.new | |
a.my_memoizable_method # :abc (prints 123) | |
a.my_memoizable_method # :abc (doesn't print 123) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Memoization | |
extend ActiveSupport::Autoload | |
autoload :MemoizedValues | |
autoload :Memoizer | |
autoload :Memoizable | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Memoization::Memoizable | |
extend ActiveSupport::Concern | |
def clear_memoization(*method_names) | |
memoized_values.reset(*method_names) | |
end | |
private | |
def call_memoized_method(original_method, *args, **kwargs, &block) | |
memoized_value = memoized_values[original_method.name] | |
parameter_types = memoizer.class.send(:method_param_types, original_method) | |
return memoized_value.first if memoized_value.present? | |
method_args = [] | |
method_args = [*args] if :args.in?(parameter_types) | |
method_args = [*method_args, **kwargs] if :kwargs.in?(parameter_types) | |
method_result = | |
if parameter_types.blank? | |
original_method.bind_call(self) | |
elsif :block.in?(parameter_types) | |
original_method.bind_call(self, *method_args, &block) | |
else | |
original_method.bind_call(self, *method_args) | |
end | |
memoized_values[original_method.name] = method_result | |
end | |
def memoized_values | |
@memoized_values ||= Memoization::MemoizedValues.new(self.class) | |
end | |
def memoizer | |
self.class.memoizer | |
end | |
class_methods do | |
def memoize(*methods) | |
methods.map(&:to_sym).each { |method| memoizer.register_method(method) } | |
end | |
def memoizer | |
class_variable_defined?(:@@memoizer) ? | |
class_variable_get(:@@memoizer) : | |
class_variable_set(:@@memoizer, Memoization::Memoizer.new(self)) | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Memoization::MemoizedValues | |
attr_reader :klass, :values | |
def initialize(klass) | |
@klass = klass | |
@values = {} | |
end | |
def register_value_for(method_name, value) | |
values[method_name] = value | |
end | |
def reset(*method_names) | |
method_names.size.zero? ? | |
@values.clear : | |
method_names.each { |method_name| @values.delete(method_name.to_sym) } | |
end | |
def value_for(method_name) | |
values.include?(method_name) ? [values[method_name]] : [] | |
end | |
alias [] value_for | |
alias []= register_value_for | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Memoization::Memoizer | |
attr_reader :klass, :memoized_methods | |
def initialize(klass) | |
@klass = klass | |
@memoized_methods = [] | |
register_method_addition_callback | |
end | |
def add_method(method_name) | |
memoized_methods << method_name unless method_name.in?(memoized_methods) | |
end | |
def create_memoized_method(method) | |
is_public_method = klass.public_method_defined?(method.name) | |
is_private_method = klass.private_method_defined?(method.name) | |
is_protected_method = klass.protected_method_defined?(method.name) | |
klass.define_method(method.name) do |*args, **kwargs, &block| | |
call_memoized_method(method, *args, **kwargs, &block) | |
end | |
klass.send(:public, method.name) if is_public_method | |
klass.send(:private, method.name) if is_private_method | |
klass.send(:protected, method.name) if is_protected_method | |
end | |
def register_method(method_name) | |
klass.instance_eval do | |
method = instance_method(method_name) rescue nil | |
memoizer.add_method(method_name) | |
memoizer.create_memoized_method(method) if method | |
end | |
end | |
private | |
def register_method_addition_callback | |
klass.instance_eval do | |
def method_added(method_name) | |
return if memoizer.memoized_methods.exclude?(method_name) | |
return if caller[0].include?('memoizer.rb') | |
memoizer.create_memoized_method(instance_method(method_name)) | |
end | |
end | |
end | |
class << self | |
private | |
def method_param_types(method) | |
method.parameters.reduce([]) do |memo, (type, _name)| | |
next memo << :args if type.in?(%i[req opt rest]) | |
next memo << :kwargs if type.in?(%i[keyreq key keyrest]) | |
memo << :block | |
end.uniq | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment