Skip to content

Commit

Permalink
Merge 6c3ae2c into 2d2b07a
Browse files Browse the repository at this point in the history
  • Loading branch information
dkubb committed Nov 21, 2013
2 parents 2d2b07a + 6c3ae2c commit 3445e25
Show file tree
Hide file tree
Showing 14 changed files with 67 additions and 320 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -4,6 +4,8 @@ source 'https://rubygems.org'

gemspec

gem 'memoizable', git: 'https://github.com/dkubb/memoizable.git'

group :development, :test do
gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
end
Expand Down
1 change: 0 additions & 1 deletion TODO
@@ -1,2 +1 @@
* Update #hash to be memoized automatically
* Refactor memoization to use memoizable
4 changes: 2 additions & 2 deletions adamantium.gemspec
Expand Up @@ -17,8 +17,8 @@ Gem::Specification.new do |gem|
gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
gem.extra_rdoc_files = %w[LICENSE README.md CONTRIBUTING.md TODO]

gem.add_runtime_dependency('ice_nine', '~> 0.10.0')
gem.add_runtime_dependency('thread_safe', '~> 0.1.3')
gem.add_runtime_dependency('ice_nine', '~> 0.10.0')
gem.add_runtime_dependency('memoizable', '~> 0.2.0')

gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
end
4 changes: 2 additions & 2 deletions config/flay.yml
@@ -1,3 +1,3 @@
---
threshold: 7
total_score: 56
threshold: 4
total_score: 32
2 changes: 1 addition & 1 deletion config/flog.yml
@@ -1,2 +1,2 @@
---
threshold: 17.5
threshold: 14.0
1 change: 1 addition & 0 deletions config/reek.yml
Expand Up @@ -41,6 +41,7 @@ LongYieldList:
NestedIterators:
enabled: true
exclude:
- Adamantium::MethodBuilder#create_memoized_method
- Adamantium::ModuleMethods#define_memoize_method
max_allowed_nesting: 1
ignore_iterators: []
Expand Down
104 changes: 3 additions & 101 deletions lib/adamantium.rb
@@ -1,14 +1,11 @@
# encoding: utf-8

require 'ice_nine'
require 'thread_safe'
require 'memoizable'

# Allows objects to be made immutable
module Adamantium

# Storage for memoized methods
Memory = Class.new(ThreadSafe::Hash)

# Defaults to less strict defaults
module Flat

Expand All @@ -17,7 +14,6 @@ module Flat
# @return [Freezer::Flat]
#
# @api private
#
def freezer
Freezer::Flat
end
Expand Down Expand Up @@ -49,59 +45,12 @@ def self.included(descendant)
#
# @api private
def self.included(descendant)
descendant.send(:include, Memoizable)
descendant.extend ModuleMethods
descendant.extend ClassMethods if descendant.kind_of?(Class)
self
end

# Freeze the object
#
# @example
# object.freeze # object is now frozen
#
# @return [Object]
#
# @api public
def freeze
memory # initialize memory
super
end

# Get the memoized value for a method
#
# @example
# hash = object.memoized(:hash)
#
# @param [Symbol] name
# the method name
#
# @return [Object]
#
# @api public
def memoized(name)
memory[name]
end

# Sets a memoized value for a method
#
# @example
# object.memoize(:hash, 12345)
#
# @param [Symbol] name
# the method name
# @param [Object] value
# the value to memoize
#
# @return [self]
#
# @api public
def memoize(name, value)
unless memory.key?(name)
store_memory(name, freeze_object(value))
end
self
end

# A noop #dup for immutable objects
#
# @example
Expand All @@ -114,56 +63,9 @@ def dup
self
end

private

# The memoized method results
#
# @return [Hash]
#
# @api private
def memory
@__memory ||= Memory.new
end

# Freeze object
#
# @param [Object] object
# an object to be frozen
#
# @return [Object]
#
# @api private
def freeze_object(object)
freezer.call(object)
end

# Return class level freezer
#
# @return [#call]
#
# @api private
def freezer
self.class.freezer
end

# Store the value in memory
#
# @param [Symbol] name
# the method name
# @param [Object] value
# the value to memoize
#
# @return [self]
#
# @return [value]
#
# @api private
def store_memory(name, value)
memory[name] = value
end

end # Adamantium

require 'adamantium/method_builder'
require 'adamantium/module_methods'
require 'adamantium/class_methods'
require 'adamantium/freezer'
Expand Down
40 changes: 40 additions & 0 deletions lib/adamantium/method_builder.rb
@@ -0,0 +1,40 @@
# encoding: utf-8

module Adamantium

# Build the memoized method using a freezer
class MethodBuilder < Memoizable::MethodBuilder

# Initialize an object to build a memoized method using a freezer
#
# @param [Module] descendant
# @param [Symbol] method_name
# @param [#call] freezer
#
# @return [undefined]
#
# @api private
def initialize(descendant, method_name, freezer)
super(descendant, method_name)
@freezer = freezer
end

private

# Create a new memoized method
#
# @return [undefined]
#
# @api private
def create_memoized_method
descendant_exec(@method_name, @original_method, @freezer) do |name, method, freezer|
define_method(name) do ||
memoized_method_cache.fetch(name) do
freezer.call(method.bind(self).call)
end
end
end
end

end # MethodBuilder
end # Adamantium
118 changes: 4 additions & 114 deletions lib/adamantium/module_methods.rb
Expand Up @@ -32,131 +32,21 @@ def memoize(*methods)
self
end

# Test if an instance method is memoized
#
# @example
# class Foo
# include Adamantium
#
# def bar
# end
# memoize :bar
#
# end
#
# Foo.memoized?(:bar) # true
# Foo.memoized?(:baz) # false, does not care if method acutally exists
#
# @param [Symbol] name
#
# @return [true]
# if method is memoized
#
# @return [false]
# otherwise
#
# @api private
def memoized?(name)
memoized_methods.key?(name)
end

# Return original instance method
#
# @example
#
# class Foo
# include Adamantium
#
# def bar
# end
# memoize :bar
#
# end
#
# Foo.original_instance_method(:bar) #=> UnboundMethod, where source_location still points to original!
#
# @param [Symbol] name
#
# @return [UnboundMethod]
# if method was memoized before
#
# @raise [ArgumentError]
# otherwise
#
# @api public
def original_instance_method(name)
memoized_methods[name]
end

private

# Memoize the named method
#
# @param [#to_s] method_name
# @param [Symbol] method_name
# a method name to memoize
# @param [#call] freezer
# a freezer for memoized values
# a callable object to freeze the value
#
# @return [undefined]
#
# @api private
def memoize_method(method_name, freezer)
method = instance_method(method_name)
if method.arity.nonzero?
fail ArgumentError, 'Cannot memoize method with nonzero arity'
end
memoized_methods[method_name] = method
visibility = method_visibility(method_name)
define_memoize_method(method, freezer)
send(visibility, method_name)
end

# Return original method registry
#
# @return [Hash<Symbol, UnboundMethod>]
#
# @api private
def memoized_methods
@memoized_methods ||= ThreadSafe::Hash.new do |_memoized_methods, name|
fail ArgumentError, "No method #{name.inspect} was memoized"
end
end

# Define a memoized method that delegates to the original method
#
# @param [UnboundMethod] method
# the method to memoize
# @param [#call] freezer
# a freezer for memoized values
#
# @return [undefined]
#
# @api private
def define_memoize_method(method, freezer)
method_name = method.name.to_sym
undef_method(method_name)
define_method(method_name) do ||
memory.fetch(method_name) do
value = method.bind(self).call
frozen = freezer.call(value)
store_memory(method_name, frozen)
end
end
end

# Return the method visibility of a method
#
# @param [String, Symbol] method
# the name of the method
#
# @return [Symbol]
#
# @api private
def method_visibility(method)
if private_method_defined?(method) then :private
elsif protected_method_defined?(method) then :protected
else :public
end
memoized_methods[method_name] = MethodBuilder
.new(self, method_name, freezer).call
end

end # ModuleMethods
Expand Down
15 changes: 0 additions & 15 deletions spec/unit/adamantium/freeze_spec.rb
Expand Up @@ -22,12 +22,6 @@
.from(false)
.to(true)
end

it 'sets a memoization instance variable' do
expect(object).to_not be_instance_variable_defined(:@__memory)
subject
expect(object.instance_variable_get(:@__memory)).to be_instance_of(Adamantium::Memory)
end
end

context 'with a frozen object' do
Expand All @@ -38,14 +32,5 @@
it 'does not change the frozen state of the object' do
expect { subject }.to_not change(object, :frozen?)
end

it 'does not change the memoization instance variable' do
expect { subject }.to_not change { object.instance_variable_get(:@__memory) }
end

it 'does not set an instance variable for memoization' do
expect(object.instance_variable_get(:@__memory)).to be_instance_of(Adamantium::Memory)
subject
end
end
end

0 comments on commit 3445e25

Please sign in to comment.