Skip to content

Commit

Permalink
Optimize no args case for Dry::Core::Memoizable
Browse files Browse the repository at this point in the history
Result of three glasses of whiskey this Friday's night
  • Loading branch information
flash-gordon committed Jul 23, 2021
1 parent 0dafeab commit d2a8d24
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 15 deletions.
2 changes: 1 addition & 1 deletion bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ require "dry/core"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

binding.irb # rubocop:disable Lint/Debugger
binding.irb
2 changes: 1 addition & 1 deletion lib/dry/core/class_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def defines(*args, type: ::Object, coerce: IDENTITY) # rubocop:disable Metrics/P
if Undefined.equal?(value)
if instance_variable_defined?(ivar)
instance_variable_get(ivar)
else # rubocop:disable Style/EmptyElse
else
nil
end
elsif type === value # rubocop:disable Style/CaseEquality
Expand Down
2 changes: 1 addition & 1 deletion lib/dry/core/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def undefined.inspect
# 1 + Undefined.default(val, 2)
# end
#
def undefined.default(x, y = self) # rubocop:disable Naming/MethodParameterName
def undefined.default(x, y = self)
if equal?(x)
if equal?(y)
yield
Expand Down
54 changes: 43 additions & 11 deletions lib/dry/core/memoizable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ def self.included(klass)

# @api private
class Memoizer < ::Module
KERNEL = {
signleton: ::Kernel.instance_method(:singleton_class),
ivar_set: ::Kernel.instance_method(:instance_variable_set),
frozen: ::Kernel.instance_method(:frozen?)
}.freeze

# @api private
def initialize(klass, names)
super()
Expand All @@ -65,20 +71,44 @@ def initialize(klass, names)
private

# @api private
def define_memoizable(method:) # rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/PerceivedComplexity
def define_memoizable(method:)
parameters = method.parameters
mod = self
kernel = KERNEL

if parameters.empty?
key = method.name.hash
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def #{method.name} # def slow_fetch
if @__memoized__.key?(#{key}) # if @__memoized__.key?(12345678)
@__memoized__[#{key}] # @__memoized__[12345678]
else # else
@__memoized__[#{key}] = super # @__memoized__[12345678] = super
end # end
end # end
RUBY
key = method.name.hash.abs

define_method(method.name) do
value = super()

if kernel[:frozen].bind(self).call
mod.remove_method(method.name)
mod.module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def #{method.name} # def slow_calc
cached = @__memoized__[#{key}] # cached = @__memoized__[12345678]
#
if cached || @__memoized__.key?(#{key}) # if cached || @__memoized__.key?(12345678)
cached # cached
else # else
@__memoized__[#{key}] = super # @__memoized__[12345678] = super
end # end
end # end
RUBY
else
attr_name = :"__memozed_#{key}__"
ivar_name = :"@#{attr_name}"
kernel[:ivar_set].bind(self).(ivar_name, value)
eigenclass = kernel[:signleton].bind(self).call
eigenclass.attr_reader(attr_name)
eigenclass.alias_method(method.name, attr_name)
eigenclass.remove_method(attr_name)
end

value
end
else
mapping = parameters.to_h { |k, v = nil| [k, v] }
params, binds = declaration(parameters, mapping)
Expand Down Expand Up @@ -112,6 +142,8 @@ def #{method.name}(#{params.join(", ")}) # def slow_calc(arg1, a
m
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/PerceivedComplexity

# @api private
def declaration(definition, lookup)
Expand Down
2 changes: 2 additions & 0 deletions spec/dry/core/memoizable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@

describe "test4" do
it_behaves_like "a memoized method" do
before { described_class.test4 }

let(:new_meth) { described_class.method(:test4) }

it "does not raise an error" do
Expand Down
2 changes: 1 addition & 1 deletion spec/support/shared_examples/memoizable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def falsey
end

RSpec.shared_examples "a memoized method" do
let(:old_meth) { new_meth.super_method }
let(:old_meth) { described_class.class.instance_method(new_meth.name) }

describe "new != old" do
subject { new_meth }
Expand Down

0 comments on commit d2a8d24

Please sign in to comment.