Optimize no args case for Dry::Core::Memoizable

Result of three glasses of whiskey this Friday's night
This commit is contained in:
Nikita Shilnikov 2021-07-23 22:22:56 +03:00
parent 0dafeab4ca
commit d2a8d24956
No known key found for this signature in database
GPG Key ID: E569D1D64C40E241
6 changed files with 49 additions and 15 deletions

View File

@ -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

View File

@ -80,7 +80,7 @@ module Dry
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

View File

@ -62,7 +62,7 @@ module Dry
# 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

View File

@ -52,6 +52,12 @@ module Dry
# @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()
@ -65,20 +71,44 @@ module Dry
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)
@ -112,6 +142,8 @@ module Dry
m
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/PerceivedComplexity
# @api private
def declaration(definition, lookup)

View File

@ -75,6 +75,8 @@ RSpec.describe Dry::Core::Memoizable do
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

View File

@ -100,7 +100,7 @@ RSpec.shared_examples "a memoizable class" do
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 }