memoizable 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTING.md +11 -0
- data/{LICENSE → LICENSE.md} +1 -1
- data/README.md +23 -0
- data/Rakefile +5 -45
- data/lib/memoizable/instance_methods.rb +47 -0
- data/lib/memoizable/memory.rb +88 -0
- data/lib/memoizable/method_builder.rb +133 -0
- data/lib/memoizable/module_methods.rb +110 -0
- data/lib/memoizable/version.rb +6 -0
- data/lib/memoizable.rb +27 -14
- data/memoizable.gemspec +18 -51
- data/spec/fixtures/classes.rb +32 -0
- data/spec/memoize_spec.rb +133 -0
- data/spec/memoized_predicate_spec.rb +26 -0
- data/spec/spec_helper.rb +22 -6
- metadata +88 -59
- data/.document +0 -5
- data/.gitignore +0 -5
- data/README.rdoc +0 -64
- data/VERSION.yml +0 -4
- data/spec/fibonacci_sample_spec.rb +0 -26
- data/spec/memoizable_spec.rb +0 -61
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Contributing
|
2
|
+
------------
|
3
|
+
|
4
|
+
* If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin.
|
5
|
+
* Fork the project.
|
6
|
+
* Make your feature addition or bug fix.
|
7
|
+
* Follow this [style guide](https://github.com/dkubb/styleguide).
|
8
|
+
* Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered.
|
9
|
+
* Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
10
|
+
* Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged.
|
11
|
+
* Send me a pull request. Bonus points for topic branches.
|
data/{LICENSE → LICENSE.md}
RENAMED
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# memoizable
|
2
|
+
|
3
|
+
Memoize method return values
|
4
|
+
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/memoizable.png)][gem]
|
6
|
+
[![Build Status](https://secure.travis-ci.org/dkubb/memoizable.png?branch=master)][travis]
|
7
|
+
[![Dependency Status](https://gemnasium.com/dkubb/memoizable.png)][gemnasium]
|
8
|
+
[![Code Climate](https://codeclimate.com/github/dkubb/memoizable.png)][codeclimate]
|
9
|
+
[![Coverage Status](https://coveralls.io/repos/dkubb/memoizable/badge.png?branch=master)][coveralls]
|
10
|
+
|
11
|
+
[gem]: https://rubygems.org/gems/memoizable
|
12
|
+
[travis]: https://travis-ci.org/dkubb/memoizable
|
13
|
+
[gemnasium]: https://gemnasium.com/dkubb/memoizable
|
14
|
+
[codeclimate]: https://codeclimate.com/github/dkubb/memoizable
|
15
|
+
[coveralls]: https://coveralls.io/r/dkubb/memoizable
|
16
|
+
|
17
|
+
## Contributing
|
18
|
+
|
19
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
20
|
+
|
21
|
+
## Copyright
|
22
|
+
|
23
|
+
Copyright © 2013 Dan Kubb, Erik Michaels-Ober. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,48 +1,8 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "memoizable"
|
8
|
-
gem.summary = %Q{Memoize method calls}
|
9
|
-
gem.description = %Q{Memoizes calls to method to boost the performance of recursive calls}
|
10
|
-
gem.email = "ecomba@nexwerk.com"
|
11
|
-
gem.homepage = "http://github.com/ecomba/memoizable"
|
12
|
-
gem.authors = ["Enrique Comba Riepenhausen"]
|
13
|
-
gem.add_development_dependency "rspec"
|
14
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
-
end
|
16
|
-
rescue LoadError
|
17
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
-
end
|
19
|
-
|
20
|
-
require 'spec/rake/spectask'
|
21
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
-
spec.libs << 'lib' << 'spec'
|
23
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
-
end
|
25
|
-
|
26
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
-
spec.libs << 'lib' << 'spec'
|
28
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
-
spec.rcov = true
|
30
|
-
end
|
31
|
-
|
32
|
-
task :spec => :check_dependencies
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
33
6
|
|
7
|
+
task :test => :spec
|
34
8
|
task :default => :spec
|
35
|
-
|
36
|
-
require 'rake/rdoctask'
|
37
|
-
Rake::RDocTask.new do |rdoc|
|
38
|
-
if File.exist?('VERSION')
|
39
|
-
version = File.read('VERSION')
|
40
|
-
else
|
41
|
-
version = ""
|
42
|
-
end
|
43
|
-
|
44
|
-
rdoc.rdoc_dir = 'rdoc'
|
45
|
-
rdoc.title = "memoizable #{version}"
|
46
|
-
rdoc.rdoc_files.include('README*')
|
47
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
-
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Memoizable
|
2
|
+
|
3
|
+
# Methods mixed in to memoizable instances
|
4
|
+
module InstanceMethods
|
5
|
+
|
6
|
+
# Freeze the object
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# object.freeze # object is now frozen
|
10
|
+
#
|
11
|
+
# @return [Object]
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def freeze
|
15
|
+
memoized_method_cache # initialize method cache
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets a memoized value for a method
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# object.memoize(hash: 12345)
|
23
|
+
#
|
24
|
+
# @param [Hash{Symbol => Object}] data
|
25
|
+
# the data to memoize
|
26
|
+
#
|
27
|
+
# @return [self]
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def memoize(data)
|
31
|
+
memoized_method_cache.set(data)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# The memoized method results
|
38
|
+
#
|
39
|
+
# @return [Hash]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
def memoized_method_cache
|
43
|
+
@_memoized_method_cache ||= Memory.new(self.class.freezer)
|
44
|
+
end
|
45
|
+
|
46
|
+
end # InstanceMethods
|
47
|
+
end # Memoizable
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Memoizable
|
2
|
+
|
3
|
+
# Storage for memoized methods
|
4
|
+
class Memory
|
5
|
+
|
6
|
+
def initialize(freezer)
|
7
|
+
@memory = ThreadSafe::Cache.new
|
8
|
+
@freezer = freezer
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get the value from memory
|
12
|
+
#
|
13
|
+
# @param [Symbol] name
|
14
|
+
#
|
15
|
+
# @return [Object]
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def [](name)
|
19
|
+
@memory.fetch(name) do
|
20
|
+
raise NameError, "No method #{name.inspect} was memoized"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Store the value in memory
|
25
|
+
#
|
26
|
+
# @param [Symbol] name
|
27
|
+
# @param [Object] value
|
28
|
+
#
|
29
|
+
# @return [undefined]
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
def []=(name, value)
|
33
|
+
if @memory.key?(name)
|
34
|
+
raise ArgumentError, "The method #{name} is already memoized"
|
35
|
+
end
|
36
|
+
@memory[name] = freeze_value(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Fetch the value from memory, or store it if it does not exist
|
40
|
+
#
|
41
|
+
# @param [Symbol] name
|
42
|
+
#
|
43
|
+
# @yieldreturn [Object]
|
44
|
+
# the value to memoize
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def fetch(name)
|
48
|
+
@memory.fetch(name) { self[name] = yield }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set the memory
|
52
|
+
#
|
53
|
+
# @param [Hash]
|
54
|
+
#
|
55
|
+
# @return [Memory]
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def set(data)
|
59
|
+
data.each { |name, value| self[name] = value }
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Test if the name has a value in memory
|
64
|
+
#
|
65
|
+
# @param [Symbol] name
|
66
|
+
#
|
67
|
+
# @return [Boolean]
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def key?(name)
|
71
|
+
@memory.key?(name)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Freeze the value
|
77
|
+
#
|
78
|
+
# @param [Object] value
|
79
|
+
#
|
80
|
+
# @return [Object]
|
81
|
+
#
|
82
|
+
# @api private
|
83
|
+
def freeze_value(value)
|
84
|
+
@freezer.call(value)
|
85
|
+
end
|
86
|
+
|
87
|
+
end # Memory
|
88
|
+
end # Memoizable
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Memoizable
|
2
|
+
|
3
|
+
# Build the memoized method
|
4
|
+
class MethodBuilder
|
5
|
+
|
6
|
+
# Raised when the method arity is invalid
|
7
|
+
class InvalidArityError < ArgumentError
|
8
|
+
|
9
|
+
# Initialize an invalid arity exception
|
10
|
+
#
|
11
|
+
# @param [Module] descendant
|
12
|
+
# @param [Symbol] method
|
13
|
+
# @param [Integer] arity
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
def initialize(descendant, method, arity)
|
17
|
+
super("Cannot memoize #{descendant}##{method}, it's arity is #{arity}")
|
18
|
+
end
|
19
|
+
|
20
|
+
end # InvalidArityError
|
21
|
+
|
22
|
+
# The original method before memoization
|
23
|
+
#
|
24
|
+
# @return [UnboundMethod]
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
attr_reader :original_method
|
28
|
+
|
29
|
+
# Initialize an object to build a memoized method
|
30
|
+
#
|
31
|
+
# @param [Module] descendant
|
32
|
+
# @param [Symbol] method_name
|
33
|
+
#
|
34
|
+
# @return [undefined]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
def initialize(descendant, method_name)
|
38
|
+
@descendant = descendant
|
39
|
+
@method_name = method_name
|
40
|
+
@original_visibility = visibility
|
41
|
+
@original_method = @descendant.instance_method(@method_name)
|
42
|
+
assert_zero_arity
|
43
|
+
end
|
44
|
+
|
45
|
+
# Build a new memoized method
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# method_builder.call # => creates new method
|
49
|
+
#
|
50
|
+
# @return [MethodBuilder]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def call
|
54
|
+
remove_original_method
|
55
|
+
create_memoized_method
|
56
|
+
set_method_visibility
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Assert the method arity is zero
|
63
|
+
#
|
64
|
+
# @return [undefined]
|
65
|
+
#
|
66
|
+
# @raise [InvalidArityError]
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
def assert_zero_arity
|
70
|
+
arity = @original_method.arity
|
71
|
+
if arity.nonzero?
|
72
|
+
raise InvalidArityError.new(@descendant, @method_name, arity)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Remove the original method
|
77
|
+
#
|
78
|
+
# @return [undefined]
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
def remove_original_method
|
82
|
+
descendant_exec(@method_name) { |name| undef_method(name) }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Create a new memoized method
|
86
|
+
#
|
87
|
+
# @return [undefined]
|
88
|
+
#
|
89
|
+
# @api private
|
90
|
+
def create_memoized_method
|
91
|
+
descendant_exec(@method_name, @original_method) do |name, method|
|
92
|
+
define_method(name) do ||
|
93
|
+
memoized_method_cache.fetch(name, &method.bind(self))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Set the memoized method visibility to match the original method
|
99
|
+
#
|
100
|
+
# @return [undefined]
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
def set_method_visibility
|
104
|
+
descendant_exec(@method_name, @original_visibility) do |name, visibility|
|
105
|
+
send(visibility, name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get the visibility of the original method
|
110
|
+
#
|
111
|
+
# @return [Symbol]
|
112
|
+
#
|
113
|
+
# @api private
|
114
|
+
def visibility
|
115
|
+
if @descendant.private_method_defined?(@method_name) then :private
|
116
|
+
elsif @descendant.protected_method_defined?(@method_name) then :protected
|
117
|
+
else :public
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Helper method to execute code within the descendant scope
|
122
|
+
#
|
123
|
+
# @param [Array] args
|
124
|
+
#
|
125
|
+
# @return [undefined]
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
def descendant_exec(*args, &block)
|
129
|
+
@descendant.instance_exec(*args, &block)
|
130
|
+
end
|
131
|
+
|
132
|
+
end # MethodBuilder
|
133
|
+
end # Memoizable
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Memoizable
|
2
|
+
|
3
|
+
# Methods mixed in to memoizable singleton classes
|
4
|
+
module ModuleMethods
|
5
|
+
|
6
|
+
# Return default deep freezer
|
7
|
+
#
|
8
|
+
# @return [#call]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
def freezer
|
13
|
+
Freezer
|
14
|
+
end
|
15
|
+
|
16
|
+
# Memoize a list of methods
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# memoize :hash
|
20
|
+
#
|
21
|
+
# @param [Array<Symbol>] methods
|
22
|
+
# a list of methods to memoize
|
23
|
+
#
|
24
|
+
# @return [self]
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def memoize(*methods)
|
28
|
+
methods.each(&method(:memoize_method))
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Test if an instance method is memoized
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# class Foo
|
36
|
+
# include Memoizable
|
37
|
+
#
|
38
|
+
# def bar
|
39
|
+
# end
|
40
|
+
# memoize :bar
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# Foo.memoized?(:bar) # true
|
44
|
+
# Foo.memoized?(:baz) # false
|
45
|
+
#
|
46
|
+
# @param [Symbol] name
|
47
|
+
#
|
48
|
+
# @return [Boolean]
|
49
|
+
# true if method is memoized, false if not
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
def memoized?(name)
|
53
|
+
memoized_methods.key?(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return unmemoized instance method
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
#
|
60
|
+
# class Foo
|
61
|
+
# include Memoizable
|
62
|
+
#
|
63
|
+
# def bar
|
64
|
+
# end
|
65
|
+
# memoize :bar
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Foo.unmemoized_instance_method(:bar)
|
69
|
+
#
|
70
|
+
# @param [Symbol] name
|
71
|
+
#
|
72
|
+
# @return [UnboundMethod]
|
73
|
+
# the memoized method
|
74
|
+
#
|
75
|
+
# @raise [NameError]
|
76
|
+
# raised if the method is unknown
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def unmemoized_instance_method(name)
|
80
|
+
memoized_methods[name].original_method
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Memoize the named method
|
86
|
+
#
|
87
|
+
# @param [Symbol] method_name
|
88
|
+
# a method name to memoize
|
89
|
+
# @param [#call] freezer
|
90
|
+
# a freezer for memoized values
|
91
|
+
#
|
92
|
+
# @return [undefined]
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def memoize_method(method_name)
|
96
|
+
memoized_methods[method_name] = MethodBuilder.new(self, method_name).call
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return method builder registry
|
100
|
+
#
|
101
|
+
# @return [Hash<Symbol, MethodBuilder>]
|
102
|
+
#
|
103
|
+
# @api private
|
104
|
+
#
|
105
|
+
def memoized_methods
|
106
|
+
@_memoized_methods ||= Memory.new(freezer)
|
107
|
+
end
|
108
|
+
|
109
|
+
end # ModuleMethods
|
110
|
+
end # Memoizable
|
data/lib/memoizable.rb
CHANGED
@@ -1,17 +1,30 @@
|
|
1
|
+
require 'thread_safe'
|
2
|
+
|
3
|
+
require 'memoizable/instance_methods'
|
4
|
+
require 'memoizable/method_builder'
|
5
|
+
require 'memoizable/module_methods'
|
6
|
+
require 'memoizable/memory'
|
7
|
+
require 'memoizable/version'
|
8
|
+
|
9
|
+
# Allow methods to be memoized
|
1
10
|
module Memoizable
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
+
|
12
|
+
# Default freezer
|
13
|
+
Freezer = lambda { |object| object.freeze }.freeze
|
14
|
+
|
15
|
+
# Hook called when module is included
|
16
|
+
#
|
17
|
+
# @param [Module] descendant
|
18
|
+
# the module or class including Memoizable
|
19
|
+
#
|
20
|
+
# @return [self]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def self.included(descendant)
|
24
|
+
descendant.module_eval do
|
25
|
+
extend ModuleMethods
|
26
|
+
include InstanceMethods
|
11
27
|
end
|
12
28
|
end
|
13
|
-
|
14
|
-
|
15
|
-
receiver.extend ClassMethods
|
16
|
-
end
|
17
|
-
end
|
29
|
+
|
30
|
+
end # Memoizable
|
data/memoizable.gemspec
CHANGED
@@ -1,56 +1,23 @@
|
|
1
|
-
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
-
# -*- encoding: utf-8 -*-
|
1
|
+
require File.expand_path('../lib/memoizable/version', __FILE__)
|
5
2
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'memoizable'
|
5
|
+
gem.version = Memoizable::VERSION.dup
|
6
|
+
gem.authors = ['Dan Kubb']
|
7
|
+
gem.email = 'dan.kubb@gmail.com'
|
8
|
+
gem.description = 'Memoize method return values'
|
9
|
+
gem.summary = gem.description
|
10
|
+
gem.homepage = 'https://github.com/dkubb/memoizable'
|
11
|
+
gem.license = 'MIT'
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
"LICENSE",
|
17
|
-
"README.rdoc"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
".gitignore",
|
22
|
-
"LICENSE",
|
23
|
-
"README.rdoc",
|
24
|
-
"Rakefile",
|
25
|
-
"VERSION.yml",
|
26
|
-
"lib/memoizable.rb",
|
27
|
-
"memoizable.gemspec",
|
28
|
-
"spec/fibonacci_sample_spec.rb",
|
29
|
-
"spec/memoizable_spec.rb",
|
30
|
-
"spec/spec_helper.rb"
|
31
|
-
]
|
32
|
-
s.homepage = %q{http://github.com/ecomba/memoizable}
|
33
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
-
s.require_paths = ["lib"]
|
35
|
-
s.rubygems_version = %q{1.3.5}
|
36
|
-
s.summary = %q{Memoize method calls}
|
37
|
-
s.test_files = [
|
38
|
-
"spec/fibonacci_sample_spec.rb",
|
39
|
-
"spec/memoizable_spec.rb",
|
40
|
-
"spec/spec_helper.rb"
|
41
|
-
]
|
13
|
+
gem.require_paths = %w[lib]
|
14
|
+
gem.files = %w[CONTRIBUTING.md LICENSE.md README.md Rakefile memoizable.gemspec]
|
15
|
+
gem.files += Dir.glob('lib/**/*.rb')
|
16
|
+
gem.files += Dir.glob('spec/**/*')
|
17
|
+
gem.test_files = Dir.glob('spec/**/*')
|
18
|
+
gem.extra_rdoc_files = %w[LICENSE.md README.md CONTRIBUTING.md]
|
42
19
|
|
43
|
-
|
44
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
-
s.specification_version = 3
|
20
|
+
gem.add_runtime_dependency('thread_safe', '~> 0.1.3')
|
46
21
|
|
47
|
-
|
48
|
-
s.add_development_dependency(%q<rspec>, [">= 0"])
|
49
|
-
else
|
50
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
51
|
-
end
|
52
|
-
else
|
53
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
54
|
-
end
|
22
|
+
gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
|
55
23
|
end
|
56
|
-
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module MemoizableSpecs
|
2
|
+
class Object
|
3
|
+
include Memoizable
|
4
|
+
|
5
|
+
def required_arguments(foo)
|
6
|
+
end
|
7
|
+
|
8
|
+
def optional_arguments(foo = nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test
|
12
|
+
'test'
|
13
|
+
end
|
14
|
+
|
15
|
+
def public_method
|
16
|
+
caller
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def protected_method
|
22
|
+
caller
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def private_method
|
28
|
+
caller
|
29
|
+
end
|
30
|
+
|
31
|
+
end # class Object
|
32
|
+
end # module MemoizableSpecs
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require File.expand_path('../fixtures/classes', __FILE__)
|
3
|
+
|
4
|
+
shared_examples_for 'memoizes method' do
|
5
|
+
it 'memoizes the instance method' do
|
6
|
+
subject
|
7
|
+
instance = object.new
|
8
|
+
expect(instance.send(method)).to be(instance.send(method))
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'creates a method that returns a same value' do
|
12
|
+
subject
|
13
|
+
instance = object.new
|
14
|
+
expect(instance.send(method)).to be(instance.send(method))
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'creates a method with an arity of 0' do
|
18
|
+
subject
|
19
|
+
expect(object.new.method(method).arity).to be_zero
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when the initializer calls the memoized method' do
|
23
|
+
before do
|
24
|
+
method = self.method
|
25
|
+
object.send(:define_method, :initialize) { send(method) }
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'allows the memoized method to be called within the initializer' do
|
29
|
+
subject
|
30
|
+
expect { object.new }.to_not raise_error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
shared_examples_for 'a command method' do
|
36
|
+
it 'returns self' do
|
37
|
+
should equal(object)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe Memoizable::ModuleMethods, '#memoize' do
|
42
|
+
subject { object.memoize(method) }
|
43
|
+
|
44
|
+
let(:object) do
|
45
|
+
stub_const 'TestClass', Class.new(MemoizableSpecs::Object) {
|
46
|
+
def some_state
|
47
|
+
Object.new
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'on method with required arguments' do
|
53
|
+
let(:method) { :required_arguments }
|
54
|
+
|
55
|
+
it 'should raise error' do
|
56
|
+
expect { subject }.to raise_error(
|
57
|
+
Memoizable::MethodBuilder::InvalidArityError,
|
58
|
+
"Cannot memoize TestClass#required_arguments, it's arity is 1"
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'on method with optional arguments' do
|
64
|
+
let(:method) { :optional_arguments }
|
65
|
+
|
66
|
+
it 'should raise error' do
|
67
|
+
expect { subject }.to raise_error(
|
68
|
+
Memoizable::MethodBuilder::InvalidArityError,
|
69
|
+
"Cannot memoize TestClass#optional_arguments, it's arity is -1"
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'memoized method that returns generated values' do
|
75
|
+
let(:method) { :some_state }
|
76
|
+
|
77
|
+
it_should_behave_like 'a command method'
|
78
|
+
it_should_behave_like 'memoizes method'
|
79
|
+
|
80
|
+
it 'creates a method that returns a frozen value' do
|
81
|
+
subject
|
82
|
+
expect(object.new.send(method)).to be_frozen
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'public method' do
|
87
|
+
let(:method) { :public_method }
|
88
|
+
|
89
|
+
it_should_behave_like 'a command method'
|
90
|
+
it_should_behave_like 'memoizes method'
|
91
|
+
|
92
|
+
it 'is still a public method' do
|
93
|
+
should be_public_method_defined(method)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'creates a method that returns a frozen value' do
|
97
|
+
subject
|
98
|
+
expect(object.new.send(method)).to be_frozen
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'protected method' do
|
103
|
+
let(:method) { :protected_method }
|
104
|
+
|
105
|
+
it_should_behave_like 'a command method'
|
106
|
+
it_should_behave_like 'memoizes method'
|
107
|
+
|
108
|
+
it 'is still a protected method' do
|
109
|
+
should be_protected_method_defined(method)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'creates a method that returns a frozen value' do
|
113
|
+
subject
|
114
|
+
expect(object.new.send(method)).to be_frozen
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'private method' do
|
119
|
+
let(:method) { :private_method }
|
120
|
+
|
121
|
+
it_should_behave_like 'a command method'
|
122
|
+
it_should_behave_like 'memoizes method'
|
123
|
+
|
124
|
+
it 'is still a private method' do
|
125
|
+
should be_private_method_defined(method)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'creates a method that returns a frozen value' do
|
129
|
+
subject
|
130
|
+
expect(object.new.send(method)).to be_frozen
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Memoizable::ModuleMethods, '#memoized?' do
|
4
|
+
let(:object) do
|
5
|
+
Class.new do
|
6
|
+
include Memoizable
|
7
|
+
def foo
|
8
|
+
end
|
9
|
+
memoize :foo
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { object.memoized?(name) }
|
14
|
+
|
15
|
+
context 'with memoized method' do
|
16
|
+
let(:name) { :foo }
|
17
|
+
|
18
|
+
it { should be(true) }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with non memoized method' do
|
22
|
+
let(:name) { :bar }
|
23
|
+
|
24
|
+
it { should be(false) }
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,25 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'simplecov'
|
2
|
+
require 'coveralls'
|
3
|
+
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
5
|
+
SimpleCov::Formatter::HTMLFormatter,
|
6
|
+
Coveralls::SimpleCov::Formatter
|
7
|
+
]
|
8
|
+
SimpleCov.start do
|
9
|
+
command_name 'spec'
|
10
|
+
|
11
|
+
add_filter 'config'
|
12
|
+
add_filter 'spec'
|
13
|
+
add_filter 'vendor'
|
14
|
+
|
15
|
+
minimum_coverage 89.8
|
16
|
+
end
|
17
|
+
|
3
18
|
require 'memoizable'
|
4
|
-
require '
|
5
|
-
require 'spec/autorun'
|
19
|
+
require 'rspec'
|
6
20
|
|
7
|
-
|
8
|
-
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.expect_with :rspec do |expect_with|
|
23
|
+
expect_with.syntax = :expect
|
24
|
+
end
|
9
25
|
end
|
metadata
CHANGED
@@ -1,77 +1,106 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: memoizable
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
|
-
authors:
|
7
|
-
-
|
7
|
+
authors:
|
8
|
+
- Dan Kubb
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
date: 2013-11-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thread_safe
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.1.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.3.5
|
17
41
|
type: :development
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.3'
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: 1.3.5
|
52
|
+
description: Memoize method return values
|
53
|
+
email: dan.kubb@gmail.com
|
27
54
|
executables: []
|
28
|
-
|
29
55
|
extensions: []
|
30
|
-
|
31
|
-
|
32
|
-
-
|
33
|
-
-
|
34
|
-
files:
|
35
|
-
- .
|
36
|
-
- .
|
37
|
-
-
|
38
|
-
- README.rdoc
|
56
|
+
extra_rdoc_files:
|
57
|
+
- LICENSE.md
|
58
|
+
- README.md
|
59
|
+
- CONTRIBUTING.md
|
60
|
+
files:
|
61
|
+
- CONTRIBUTING.md
|
62
|
+
- LICENSE.md
|
63
|
+
- README.md
|
39
64
|
- Rakefile
|
40
|
-
- VERSION.yml
|
41
|
-
- lib/memoizable.rb
|
42
65
|
- memoizable.gemspec
|
43
|
-
-
|
44
|
-
-
|
66
|
+
- lib/memoizable/instance_methods.rb
|
67
|
+
- lib/memoizable/memory.rb
|
68
|
+
- lib/memoizable/method_builder.rb
|
69
|
+
- lib/memoizable/module_methods.rb
|
70
|
+
- lib/memoizable/version.rb
|
71
|
+
- lib/memoizable.rb
|
72
|
+
- spec/fixtures/classes.rb
|
73
|
+
- spec/memoize_spec.rb
|
74
|
+
- spec/memoized_predicate_spec.rb
|
45
75
|
- spec/spec_helper.rb
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
76
|
+
homepage: https://github.com/dkubb/memoizable
|
77
|
+
licenses:
|
78
|
+
- MIT
|
50
79
|
post_install_message:
|
51
|
-
rdoc_options:
|
52
|
-
|
53
|
-
require_paths:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
54
82
|
- lib
|
55
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
67
95
|
requirements: []
|
68
|
-
|
69
96
|
rubyforge_project:
|
70
|
-
rubygems_version: 1.
|
97
|
+
rubygems_version: 1.8.23
|
71
98
|
signing_key:
|
72
99
|
specification_version: 3
|
73
|
-
summary: Memoize method
|
74
|
-
test_files:
|
75
|
-
- spec/
|
76
|
-
- spec/
|
100
|
+
summary: Memoize method return values
|
101
|
+
test_files:
|
102
|
+
- spec/fixtures/classes.rb
|
103
|
+
- spec/memoize_spec.rb
|
104
|
+
- spec/memoized_predicate_spec.rb
|
77
105
|
- spec/spec_helper.rb
|
106
|
+
has_rdoc:
|
data/.document
DELETED
data/README.rdoc
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
= Memoizable
|
2
|
-
|
3
|
-
== Introduction
|
4
|
-
|
5
|
-
When doing recursion sometimes we are faced with the problem of performance
|
6
|
-
versus the beauty of the code we are implementing, specially when, due to
|
7
|
-
the recursive nature of the method we have written we seem to be computing
|
8
|
-
over and over the same.
|
9
|
-
|
10
|
-
The concept of memoization comes from the idea of capturing a certain
|
11
|
-
method call and saving it's result to an internal cache, so that, if this
|
12
|
-
particular method (with the same parameters) is called, it will return the
|
13
|
-
previously computed result.
|
14
|
-
|
15
|
-
I was inspired in writing this little module after reading an article
|
16
|
-
on James Edward Grey II'2 blog (http://blog.grayproductions.net/articles/caching_and_memoization)
|
17
|
-
|
18
|
-
== Example
|
19
|
-
|
20
|
-
Imagine we want to compute the Fibonacci sequence:
|
21
|
-
|
22
|
-
class Fibonacci
|
23
|
-
def fib(num)
|
24
|
-
return num if num < 2
|
25
|
-
fib(num -1) + fib(num - 2)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
As you can see immediately is that this will result in poor performance the
|
30
|
-
higher the number in the sequence we request as the algorithm will compute
|
31
|
-
over and over same method calls.
|
32
|
-
|
33
|
-
You could add some cache functionallity into your method and class, although
|
34
|
-
you would add new behaviour that the class and method shouldn't really have
|
35
|
-
as they should be dealing exclusively with the logic they are supposed to
|
36
|
-
execute; in this case the computation of the Fibonacci sequence.
|
37
|
-
|
38
|
-
Here is how the class would look like when we Memonize it:
|
39
|
-
|
40
|
-
class Fibonacci
|
41
|
-
include Memoizable
|
42
|
-
|
43
|
-
def fib(num)
|
44
|
-
return num if num < 2
|
45
|
-
fib(num -1) + fib(num - 2)
|
46
|
-
end
|
47
|
-
|
48
|
-
memoize :fib
|
49
|
-
end
|
50
|
-
|
51
|
-
== Note on Patches/Pull Requests
|
52
|
-
|
53
|
-
* Fork the project.
|
54
|
-
* Make your feature addition or bug fix.
|
55
|
-
* Add tests for it. This is important so I don't break it in a
|
56
|
-
future version unintentionally.
|
57
|
-
* Commit, do not mess with rakefile, version, or history.
|
58
|
-
(if you want to have your own version, that is fine but
|
59
|
-
bump version in a commit by itself I can ignore when I pull)
|
60
|
-
* Send me a pull request. Bonus points for topic branches.
|
61
|
-
|
62
|
-
== Copyright
|
63
|
-
|
64
|
-
Copyright (c) 2009 Enrique Comba Riepenhausen. See LICENSE for details.
|
data/VERSION.yml
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
|
3
|
-
require 'benchmark'
|
4
|
-
|
5
|
-
describe "Fibonacci" do
|
6
|
-
class Fibonacci
|
7
|
-
def fib(number)
|
8
|
-
return number if number < 2
|
9
|
-
fib(number -1) + fib(number - 2)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
context "context" do
|
14
|
-
it "should run faster" do
|
15
|
-
fibo = Fibonacci.new
|
16
|
-
bm = Benchmark.measure { fibo.fib(30) }
|
17
|
-
class Fibonacci; include Memoizable; memoize :fib; end;
|
18
|
-
fibo2 = Fibonacci.new
|
19
|
-
bm2 = Benchmark.measure { fibo2.fib(30) }
|
20
|
-
|
21
|
-
# This test is flakey, I know... How to test the real speed?
|
22
|
-
bm.to_a[5].should > bm2.to_a[5]
|
23
|
-
bm2.to_a[5].should < 0.01
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
data/spec/memoizable_spec.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
|
3
|
-
describe "Memoizable" do
|
4
|
-
before(:all) do
|
5
|
-
class Foo; def a; return "blaah" end; end;
|
6
|
-
end
|
7
|
-
|
8
|
-
context "Module structure" do
|
9
|
-
it "should have a ClassMethods module" do
|
10
|
-
Memoizable::ClassMethods.class.should be(Module)
|
11
|
-
end
|
12
|
-
|
13
|
-
it "should contain a memonize method" do
|
14
|
-
Memoizable::ClassMethods.public_method_defined?(:memoize).should be_true
|
15
|
-
end
|
16
|
-
|
17
|
-
context ": Cache" do
|
18
|
-
it "should be present" do
|
19
|
-
Memoizable::CACHE.should_not be_nil
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should be a hash" do
|
23
|
-
Memoizable::CACHE.should be_instance_of(Hash)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
context "Class Inclusion" do
|
29
|
-
it "should extend a given class with the memoize method" do
|
30
|
-
class Foo; include Memoizable; end;
|
31
|
-
Foo.respond_to?(:memoize).should be_true
|
32
|
-
end
|
33
|
-
|
34
|
-
it "should alias the original method" do
|
35
|
-
Foo.memoize :a
|
36
|
-
foo = Foo.new
|
37
|
-
foo.respond_to?("__original__a").should be_true
|
38
|
-
end
|
39
|
-
|
40
|
-
it "should modify the method" do
|
41
|
-
method_a = Foo.instance_method(:a)
|
42
|
-
Foo.memoize :a
|
43
|
-
method_a.should_not == Foo.instance_method(:a)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
context "Method calls" do
|
48
|
-
it "should return the same values" do
|
49
|
-
foo = Foo.new
|
50
|
-
first_return = foo.a
|
51
|
-
Foo.memoize :a
|
52
|
-
first_return.should == foo.a
|
53
|
-
end
|
54
|
-
|
55
|
-
it "should put the method call into the cache" do
|
56
|
-
Foo.memoize :a
|
57
|
-
foo = Foo.new
|
58
|
-
Memoizable::CACHE.size.should > 0
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|