memoized_on_frozen 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +51 -0
- data/README.md +119 -0
- data/Rakefile +10 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/example.rb +36 -0
- data/lib/memoized_on_frozen.rb +77 -0
- data/lib/memoized_on_frozen/as_memoized.rb +4 -0
- data/lib/memoized_on_frozen/version.rb +5 -0
- data/memoized_on_frozen.gemspec +28 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7b9b5a0421f853e1b9756a4803bc49c70465d202f0f4a6ba3d9af8132be8e83f
|
4
|
+
data.tar.gz: dc0a3ed91fae98a822084385a8f8a944e9661baff749cf13866200cc62ddcd3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d7810466303db44765c16141c0a91d902b4dd4cc4430f8f3fc4d698cf81c2f2f1990a3615773178b0db02b3fd8c3aa732f6f3d84dbfe739b6fc8d93bd5ae6236
|
7
|
+
data.tar.gz: c5f78499029c9ccc8f982f1b582b61af55d8608e65d1e4e852216d94119fa1a0f06f7d38d00f75c824dfd880da0a10f4e86613dd0ec344b9533a4be843a8f161
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
memoized_on_frozen (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.0)
|
10
|
+
diff-lcs (1.3)
|
11
|
+
parallel (1.12.1)
|
12
|
+
parser (2.5.0.5)
|
13
|
+
ast (~> 2.4.0)
|
14
|
+
powerpack (0.1.1)
|
15
|
+
rainbow (3.0.0)
|
16
|
+
rake (10.5.0)
|
17
|
+
rspec (3.7.0)
|
18
|
+
rspec-core (~> 3.7.0)
|
19
|
+
rspec-expectations (~> 3.7.0)
|
20
|
+
rspec-mocks (~> 3.7.0)
|
21
|
+
rspec-core (3.7.1)
|
22
|
+
rspec-support (~> 3.7.0)
|
23
|
+
rspec-expectations (3.7.0)
|
24
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
+
rspec-support (~> 3.7.0)
|
26
|
+
rspec-mocks (3.7.0)
|
27
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
28
|
+
rspec-support (~> 3.7.0)
|
29
|
+
rspec-support (3.7.1)
|
30
|
+
rubocop (0.54.0)
|
31
|
+
parallel (~> 1.10)
|
32
|
+
parser (>= 2.5)
|
33
|
+
powerpack (~> 0.1)
|
34
|
+
rainbow (>= 2.2.2, < 4.0)
|
35
|
+
ruby-progressbar (~> 1.7)
|
36
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
37
|
+
ruby-progressbar (1.9.0)
|
38
|
+
unicode-display_width (1.3.0)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
bundler (~> 1.16)
|
45
|
+
memoized_on_frozen!
|
46
|
+
rake (~> 10.0)
|
47
|
+
rspec (~> 3.0)
|
48
|
+
rubocop
|
49
|
+
|
50
|
+
BUNDLED WITH
|
51
|
+
1.16.1
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# MemoizedOnFrozen
|
2
|
+
|
3
|
+
[](https://travis-ci.org/iliabylich/memoized_on_frozen)
|
4
|
+
|
5
|
+
Immutable objects are a good thing, but they can't use memoization:
|
6
|
+
|
7
|
+
``` ruby
|
8
|
+
class Rectangle
|
9
|
+
def initialize(x, y)
|
10
|
+
@x = x
|
11
|
+
@y = y
|
12
|
+
|
13
|
+
freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def area
|
17
|
+
@area ||= @x * @y
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Rectangle.new(2, 3).area
|
22
|
+
# => FrozenError (can't modify frozen Rectangle)
|
23
|
+
```
|
24
|
+
|
25
|
+
This gem offers a very simple solution - it performs a memoization on a "global" registry object.
|
26
|
+
|
27
|
+
Like this:
|
28
|
+
``` ruby
|
29
|
+
def area
|
30
|
+
$registry[self][:area] ||= @x * @y
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
But then we face another problem: memory leaking. Every object that is memoized creates a garbage
|
35
|
+
in the "global" variable that is never GC-ed. This can be solved using `WeakRef` Ruby class.
|
36
|
+
|
37
|
+
`MemoizedOnFrozen` uses a `Hash` as a registry under the hood, but this hash uses `WeakRef`s as keys.
|
38
|
+
|
39
|
+
## Example
|
40
|
+
|
41
|
+
Declare a class:
|
42
|
+
``` ruby
|
43
|
+
require 'memoized_on_frozen'
|
44
|
+
|
45
|
+
class Rectangle
|
46
|
+
include MemoizedOnFrozen[:area]
|
47
|
+
|
48
|
+
def initialize(x, y)
|
49
|
+
@x = x
|
50
|
+
@y = y
|
51
|
+
|
52
|
+
freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
def area
|
56
|
+
puts 'calculating area'
|
57
|
+
@x * @y
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
And see how the library handles it:
|
63
|
+
|
64
|
+
``` ruby
|
65
|
+
rectangle1 = Rectangle.new(1, 2)
|
66
|
+
rectangle2 = Rectangle.new(3, 4)
|
67
|
+
|
68
|
+
rectangle1.area
|
69
|
+
# calculating area
|
70
|
+
# => 2
|
71
|
+
rectangle1.area
|
72
|
+
# => 2
|
73
|
+
|
74
|
+
rectangle2.area
|
75
|
+
# calculating area
|
76
|
+
# => 12
|
77
|
+
rectangle2.area
|
78
|
+
# => 12
|
79
|
+
|
80
|
+
MemoizedOnFrozen::WEAK_STORE.keys
|
81
|
+
# => [rectangle1, rectangle2]
|
82
|
+
|
83
|
+
rectangle1 = rectangle2 = nil
|
84
|
+
GC.start
|
85
|
+
MemoizedOnFrozen::WEAK_STORE.keys
|
86
|
+
# => []
|
87
|
+
```
|
88
|
+
|
89
|
+
You can find a working example in `example.rb`
|
90
|
+
|
91
|
+
## Installation
|
92
|
+
|
93
|
+
Add this line to your application's Gemfile:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
gem 'memoized_on_frozen'
|
97
|
+
```
|
98
|
+
|
99
|
+
Or if you'd like to use an alias `Memoized` instead of `MemoizedOnFrozen`
|
100
|
+
(and you are 100% sure that there are no conflicts with existing classes):
|
101
|
+
|
102
|
+
``` ruby
|
103
|
+
gem 'memoized_on_frozen', require: 'memoized_on_frozen/as_memoized'
|
104
|
+
```
|
105
|
+
|
106
|
+
The file `lib/memoized_on_frozen/as_memoized.rb` literally does `Memoized = MemoizedOnFrozen`
|
107
|
+
|
108
|
+
And then execute:
|
109
|
+
|
110
|
+
$ bundle
|
111
|
+
|
112
|
+
Or install it yourself as:
|
113
|
+
|
114
|
+
$ gem install memoized_on_frozen
|
115
|
+
|
116
|
+
|
117
|
+
## Contributing
|
118
|
+
|
119
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/iliabylich/memoized_on_frozen.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'memoized_on_frozen'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/example.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# bundle exec ruby example.rb
|
2
|
+
|
3
|
+
require 'memoized_on_frozen'
|
4
|
+
|
5
|
+
class Rectangle
|
6
|
+
include MemoizedOnFrozen[:area]
|
7
|
+
|
8
|
+
def initialize(x, y)
|
9
|
+
@x = x
|
10
|
+
@y = y
|
11
|
+
|
12
|
+
freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def area
|
16
|
+
puts 'calculating area'
|
17
|
+
@x * @y
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
rectangle1 = Rectangle.new(1, 2)
|
22
|
+
rectangle2 = Rectangle.new(3, 4)
|
23
|
+
|
24
|
+
puts rectangle1.area
|
25
|
+
puts rectangle1.area
|
26
|
+
|
27
|
+
puts rectangle2.area
|
28
|
+
puts rectangle2.area
|
29
|
+
|
30
|
+
p MemoizedOnFrozen::WEAK_STORE.keys
|
31
|
+
# => [rectangle1, rectangle2]
|
32
|
+
|
33
|
+
rectangle1 = rectangle2 = nil
|
34
|
+
GC.start
|
35
|
+
p MemoizedOnFrozen::WEAK_STORE.keys
|
36
|
+
# => []
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memoized_on_frozen/version'
|
4
|
+
require 'weakref'
|
5
|
+
|
6
|
+
# A builder module that allows you to do memoization on
|
7
|
+
# frozen objects
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# class YourClass
|
11
|
+
# include MemoizedOnFrozen[:m]
|
12
|
+
#
|
13
|
+
# def initialize
|
14
|
+
# freeze
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def m
|
18
|
+
# some_heavy_computation
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
module MemoizedOnFrozen
|
23
|
+
# :nodoc:
|
24
|
+
class WeakStore < Hash
|
25
|
+
(Hash.instance_methods(false) - [:select!]).each do |method_name|
|
26
|
+
define_method(method_name) do |*args, &blk|
|
27
|
+
cleanup if potentially_stale?
|
28
|
+
super(*args, &blk)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(*)
|
33
|
+
super
|
34
|
+
@last_tracked_gc_run = GC.count
|
35
|
+
end
|
36
|
+
|
37
|
+
def cleanup
|
38
|
+
select! { |key, _| key.weakref_alive? }
|
39
|
+
@last_tracked_gc_run = GC.count
|
40
|
+
end
|
41
|
+
|
42
|
+
def potentially_stale?
|
43
|
+
@last_tracked_gc_run != GC.count
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
WEAK_STORE = WeakStore.new
|
48
|
+
|
49
|
+
# :nodoc:
|
50
|
+
module SetWeakRef
|
51
|
+
def initialize(*args, &block)
|
52
|
+
@_ref = WeakRef.new(self)
|
53
|
+
WEAK_STORE[@_ref] = {}
|
54
|
+
super(*args, &block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
def _module_for(method_names)
|
60
|
+
Module.new do
|
61
|
+
method_names.each do |method_name|
|
62
|
+
define_method(method_name) do
|
63
|
+
WEAK_STORE[@_ref][method_name] ||= super()
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](*method_names)
|
70
|
+
Module.new do
|
71
|
+
define_singleton_method(:included) do |base|
|
72
|
+
base.prepend(SetWeakRef, MemoizedOnFrozen._module_for(method_names))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib = File.expand_path('lib', __dir__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require 'memoized_on_frozen/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'memoized_on_frozen'
|
10
|
+
spec.version = MemoizedOnFrozen::VERSION
|
11
|
+
spec.authors = ['Ilya Bylich']
|
12
|
+
spec.email = ['ibylich@gmail.com']
|
13
|
+
|
14
|
+
spec.summary = 'A tiny gem for memoization on frozen objects'
|
15
|
+
spec.description = 'Because memoization creates an ivar'
|
16
|
+
spec.homepage = 'https://github.com/iliabylich/memoized_on_frozen'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: memoized_on_frozen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ilya Bylich
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Because memoization creates an ivar
|
56
|
+
email:
|
57
|
+
- ibylich@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- Gemfile.lock
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- example.rb
|
72
|
+
- lib/memoized_on_frozen.rb
|
73
|
+
- lib/memoized_on_frozen/as_memoized.rb
|
74
|
+
- lib/memoized_on_frozen/version.rb
|
75
|
+
- memoized_on_frozen.gemspec
|
76
|
+
homepage: https://github.com/iliabylich/memoized_on_frozen
|
77
|
+
licenses: []
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.7.3
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: A tiny gem for memoization on frozen objects
|
99
|
+
test_files: []
|