memonymous 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +51 -0
- data/Rakefile +1 -0
- data/lib/memonymous.rb +41 -0
- data/lib/memonymous/version.rb +3 -0
- data/memonymous.gemspec +20 -0
- data/test/memonymous_test.rb +57 -0
- data/test/test_helper.rb +5 -0
- metadata +68 -0
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.2-p180@memonymous --create --install
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
7
|
+
copies of this license document, and changing it is allowed as long
|
8
|
+
as the name is changed.
|
9
|
+
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
|
+
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Memonymous: a Ruby memoization module that doesn't clutter up its includees.
|
2
|
+
|
3
|
+
## License
|
4
|
+
|
5
|
+
Public domain (more or less). See the LICENSE file for more information.
|
6
|
+
|
7
|
+
## Origin
|
8
|
+
|
9
|
+
Inspired by a recent Hangman puzzle at the Portland Ruby Brigade ([link](http://groups.google.com/group/pdxruby/browse_thread/thread/19d58126a268e89a/078efefc26ddcf7b)), I decided to try writing a memoization module that used `super` instead of mucking around with `alias_method_chain`.
|
10
|
+
|
11
|
+
Unfortunately, the only way I could think of to get this to work was to override the including class's .new method, so that all new instances were actually instances of a newly-created subclass...and that just seemed like a Really Bad Idea™.
|
12
|
+
|
13
|
+
Fortunately, I did find an interesting workaround, and in the process learned a few new tricks that have been sitting right there in the Kernel and Module classes just waiting for me to notice them.
|
14
|
+
|
15
|
+
## Usage:
|
16
|
+
|
17
|
+
<pre>
|
18
|
+
class FrankSinatra
|
19
|
+
attr_reader :call_count
|
20
|
+
def dont_be_afraid_you_can_call_me
|
21
|
+
@call_count ||= 0
|
22
|
+
@call_count += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class MemoizedFrankSinatra < FrankSinatra
|
27
|
+
include Memonymous
|
28
|
+
memoize :dont_be_afraid_you_can_call_me
|
29
|
+
end
|
30
|
+
|
31
|
+
naked_frank = FrankSinatra.new
|
32
|
+
3.times do
|
33
|
+
naked_frank.dont_be_afraid_you_can_call_me
|
34
|
+
end
|
35
|
+
p naked_frank.call_count # => 3
|
36
|
+
|
37
|
+
memoized_frank = MemoizedFrankSinatra.new
|
38
|
+
3.times do
|
39
|
+
memoized_frank.dont_be_afraid_you_can_call_me
|
40
|
+
end
|
41
|
+
p memoized_frank.call_count # => 1
|
42
|
+
</pre>
|
43
|
+
|
44
|
+
## Technique
|
45
|
+
|
46
|
+
I was actually quite amused by this one. Because Ruby doesn't let you change the superclass of a Class object, I couldn't insert new methods above the includee in the hierarchy. Instead, when .memoize is called, it asks Ruby for an UnboundMethod object for each method it's supposed to memoize and hangs onto it in a class instance variable. Then it defines its own replacement for the same method.
|
47
|
+
|
48
|
+
Later, when you call the memoized method, we either look to see if we've already computed it (thus the 'memoization' part of the gem), or we grab the UnboundMethod, bind it to the caller, call it, and save the result. This process reminds me of the Wallace and Gromit film "The Wrong Trousers" -- see the comments in the Memonymous module for a link to a Google video search.
|
49
|
+
|
50
|
+
Thanks also to Jay Fields for the technique -- I had started down this path and was trying to figure it out, but came across a [2008 blog post](http://blog.jayfields.com/2008/04/alternatives-for-redefining-methods.html) with a conveniently-packaged code snippet.
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/memonymous.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "memonymous/version"
|
2
|
+
|
3
|
+
module Memonymous
|
4
|
+
def self.included(receiver)
|
5
|
+
receiver.extend ClassMethod
|
6
|
+
unless receiver.instance_variable_defined?('@memoized_methods')
|
7
|
+
receiver.instance_variable_set('@memoized_methods', {})
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Note the singular name!
|
12
|
+
# The point of this exercise is to memoize without cluttering up the function namespace.
|
13
|
+
module ClassMethod
|
14
|
+
def memoize(*methods)
|
15
|
+
Array(methods).flatten.map(&:to_sym).each do |mname|
|
16
|
+
@memoized_methods[mname] = instance_method(mname) # save the UnboundMethod for later
|
17
|
+
class_eval <<-RUBY
|
18
|
+
# apparently 'def' is faster than 'define_method', according to Several Random People on the Internet
|
19
|
+
def #{mname}(*args, &b)
|
20
|
+
ivar_name = '@#{mname}'
|
21
|
+
|
22
|
+
# The entire point: returning fast if we already know the answer
|
23
|
+
return instance_variable_get(ivar_name) if instance_variable_defined?(ivar_name)
|
24
|
+
|
25
|
+
# FIRST POST^H^H^H^H CALL!
|
26
|
+
# Grab the UnboundMethod, bind it, and call it.
|
27
|
+
# I like to think of this as the "Gromit's Train Track" approach to memoization.
|
28
|
+
# (See: http://www.google.com/search?q=wallace+and+gromit+train+chase if you don't get the reference.)
|
29
|
+
return instance_variable_set( ivar_name,
|
30
|
+
self \
|
31
|
+
.class \
|
32
|
+
.instance_variable_get('@memoized_methods')[:'#{mname}'] \
|
33
|
+
.bind(self) \
|
34
|
+
.call(*args, &b)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
RUBY
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/memonymous.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "memonymous/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "memonymous"
|
7
|
+
s.version = Memonymous::VERSION
|
8
|
+
s.authors = ["Sam Livingston-Gray"]
|
9
|
+
s.email = ["geeksam@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Yet another memoizing gem. This one doesn't pollute your method namespace.}
|
12
|
+
s.description = %q{VERY SIMPLISTIC memoization for functions (one memoized value per function; no regard for arguments). Uses UnboundMethods instead of method aliasing, so you don't see extra method names when you inspect your object.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "memonymous"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), *%w[test_helper]))
|
2
|
+
|
3
|
+
describe Memonymous do
|
4
|
+
before do
|
5
|
+
@memoize_this = Class.new do
|
6
|
+
attr_reader :call_count
|
7
|
+
def dont_be_afraid_you_can_call_me
|
8
|
+
@call_count ||= 0
|
9
|
+
@call_count += 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should add a .memoize method' do
|
15
|
+
refute @memoize_this.respond_to?(:memoize), "Method found before it should be there"
|
16
|
+
@memoize_this.send(:include, Memonymous)
|
17
|
+
assert @memoize_this.respond_to?(:memoize), "Method not found after it should've been added"
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should ONLY add a .memoize method' do
|
21
|
+
old_klass_methods = @memoize_this.methods.sort.dup
|
22
|
+
old_inst_methods = @memoize_this.instance_methods.sort.dup
|
23
|
+
|
24
|
+
@memoize_this.send(:include, Memonymous)
|
25
|
+
|
26
|
+
new_klass_methods = @memoize_this.methods.sort.dup
|
27
|
+
new_inst_methods = @memoize_this.instance_methods.sort.dup
|
28
|
+
|
29
|
+
assert_equal [:memoize], new_klass_methods - old_klass_methods
|
30
|
+
assert_equal [], new_inst_methods - old_inst_methods
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'when included into a class' do
|
34
|
+
before :each do
|
35
|
+
@memoize_this.send(:include, Memonymous)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should not memoize methods it hasn't been told about" do
|
39
|
+
foo = @memoize_this.new
|
40
|
+
foo.dont_be_afraid_you_can_call_me
|
41
|
+
foo.dont_be_afraid_you_can_call_me
|
42
|
+
assert_equal 2, foo.call_count
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should set up a @memoized_methods class instance variable' do
|
46
|
+
assert @memoize_this.instance_variables.map(&:to_sym).include?(:'@memoized_methods')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should memoize methods it has been told about' do
|
50
|
+
@memoize_this.memoize(:dont_be_afraid_you_can_call_me)
|
51
|
+
foo = @memoize_this.new
|
52
|
+
foo.dont_be_afraid_you_can_call_me
|
53
|
+
foo.dont_be_afraid_you_can_call_me
|
54
|
+
assert_equal 1, foo.call_count
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: memonymous
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sam Livingston-Gray
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-27 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: VERY SIMPLISTIC memoization for functions (one memoized value per function; no regard for arguments). Uses UnboundMethods instead of method aliasing, so you don't see extra method names when you inspect your object.
|
18
|
+
email:
|
19
|
+
- geeksam@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- .gitignore
|
28
|
+
- .rvmrc
|
29
|
+
- Gemfile
|
30
|
+
- LICENSE
|
31
|
+
- README.md
|
32
|
+
- Rakefile
|
33
|
+
- lib/memonymous.rb
|
34
|
+
- lib/memonymous/version.rb
|
35
|
+
- memonymous.gemspec
|
36
|
+
- test/memonymous_test.rb
|
37
|
+
- test/test_helper.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: ""
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project: memonymous
|
62
|
+
rubygems_version: 1.6.0
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Yet another memoizing gem. This one doesn't pollute your method namespace.
|
66
|
+
test_files:
|
67
|
+
- test/memonymous_test.rb
|
68
|
+
- test/test_helper.rb
|