laziest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 29f110db9af135c7252ca69456131c27bc35e299
4
+ data.tar.gz: 17b87349963fd750fce3f8c34b80db66d1eeac65
5
+ SHA512:
6
+ metadata.gz: b92d11825b05e450eaf6ffe472c03f2400d7e8d481b8e0eed9aa394ca9eeb743ddda5c4397a648c29d6031ad5be194a1e299b16e50cbe173a0f147776ab95ab3
7
+ data.tar.gz: 13be6da5325bcef78f8da3580bd926903f9d4f378d47445abf6c2d876c388de4f039c9b2b81483474be22d7ba66eae8bc9d53c847acc6d4b6f6b4e78b3404c82
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in lazy_count.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Laziest
2
+
3
+ Extends Enumerable#lazy (aka Enumerator::Lazy) with additional
4
+ opportunities for lazy and especially *partial* evaluation.
5
+ For example:
6
+
7
+ (1..Float::INFINITY).lazy.count > 10
8
+
9
+ evaluates almost instantaneously.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'laziest'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install laziest
24
+
25
+ ## Usage
26
+
27
+ Simply add this before working with any lazy enumerators:
28
+
29
+ require 'laziest'
30
+
31
+ This will extend Enumerator::Lazy (and the Enumerable#lazy method)
32
+ to have lazier versions of the following methods:
33
+
34
+ * chunk
35
+ * count
36
+ * entries
37
+ * group_by
38
+ * max
39
+ * min
40
+ * minmax
41
+ * partition
42
+ * slice_before
43
+ * to_a
44
+
45
+ The somewhat ambitious goal is to defer as much as possible, while maintaining
46
+ full compatibility with the existing API. Some more examples:
47
+
48
+ (1..Float::INFINITY).lazy.group_by{|x| x.to_s.length}[3].first(2) # => [100, 101]
49
+ Prime.lazy.max > 1000 # => true
50
+ Prime.lazy.partition{|x| x%10 == 1}.all? {|x| x.count > 100} # => true
51
+
52
+ As with any self-respecting Ruby laziness, proxy classes
53
+ (specifically, Promises from the promise ("promising future") gem)
54
+ are abused, to ensure that traditional, non-lazy usage is correct.
55
+
56
+ (1..5).lazy.group_by(&:even?) # => {false=>[1, 3, 5], true=>[2, 4]}
57
+
58
+ Not everything is as lazy as possible yet, but anything not lazy should still
59
+ work, eventually, on anything finite.
60
+
61
+ Warning: Be careful in IRB! Remember that IRB calls #inspect on the result
62
+ of any expression you type. This will almost always force a complete
63
+ evaluation. For example, if you type this:
64
+
65
+ evens, odds = (1..Float::INFINITY).lazy.partition(&:even?)
66
+
67
+ This will attempt to #inspect the resulting arrays, and you have created an
68
+ infinite loop. You can avoid this by adding a useless expression to the end:
69
+
70
+ evens, odds = (1..Float::INFINITY).lazy.partition(&:even?); nil
71
+ evens.first(10)
72
+
73
+ Of course, abusing one-liners is the easiest way to avoid this:
74
+
75
+ (1..Float::INFINITY).lazy.group_by(&:even?)[true].first(10)
76
+
77
+ ## Contributing
78
+
79
+ 1. Fork it
80
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
81
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
82
+ 4. Push to the branch (`git push origin my-new-feature`)
83
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/laziest.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'laziest/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'laziest'
8
+ spec.version = Laziest::VERSION
9
+ spec.authors = ['David Masover']
10
+ spec.email = ['masover@iastate.edu']
11
+ spec.description = 'The laziest possible enumerables and enumerators'
12
+ spec.summary = <<-END
13
+ When there's just no O(1) way to compute something, this gem provides
14
+ both promises (lazy evaluation) and partial evaluation, along with
15
+ implicit, softref-based memoization. For example, (foo.lazy.count > 5) will
16
+ invoke the iterator at most six times.
17
+ END
18
+ spec.homepage = ''
19
+ spec.license = 'MIT'
20
+
21
+ spec.files = `git ls-files`.split($/)
22
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+
29
+ spec.add_dependency 'promise'
30
+ spec.add_dependency 'soft_reference'
31
+ end
data/lib/laziest.rb ADDED
@@ -0,0 +1,14 @@
1
+ autoload :Promise, 'promise'
2
+
3
+ module Laziest
4
+ autoload :VERSION, 'laziest/version'
5
+ autoload :Counter, 'laziest/counter'
6
+ autoload :ArrayPromise, 'laziest/array'
7
+ autoload :Group, 'laziest/group'
8
+ autoload :MutexUtil, 'laziest/mutex_util'
9
+ autoload :MinMax, 'laziest/minmax'
10
+
11
+ require 'laziest/extensions'
12
+
13
+ Enumerator::Lazy.send :include, Extensions::LazyEnumerator
14
+ end
@@ -0,0 +1,109 @@
1
+ module Laziest
2
+ # This class is a lazily-evaluated array based on an enumerator.
3
+ # A contract: The enumerator is guaranteed to never be rewound.
4
+ class ArrayPromise < Promise
5
+ # Accepts 'array' to allow a partially-evaluated enumerator/array to initialize.
6
+ def initialize enumerator, array=[], nil_on_empty = false
7
+ @array = array
8
+ @enumerator = enumerator
9
+ @nil_on_empty = nil_on_empty
10
+ super() do
11
+ ::Kernel.loop do
12
+ @array << @enumerator.next
13
+ end
14
+ @enumerator = nil
15
+ # Needed for use in hashes, like group_by
16
+ (nil_on_empty && @array.empty?) ? nil : @array
17
+ end
18
+ end
19
+
20
+ def each(&block)
21
+ return super if __forced__?
22
+ return ::Enumerator.new{|y| each {|x| y << x}}.lazy unless ::Kernel.block_given?
23
+ index = 0
24
+ ::Kernel.loop do
25
+ __force_to__ index
26
+ break unless @array.length > index
27
+ yield @array[index]
28
+ index += 1
29
+ end
30
+ end
31
+
32
+ # Array-related laziness
33
+ def [] index, length=nil
34
+ return super if __forced__?
35
+ if length.nil?
36
+ if index.kind_of? ::Range
37
+ enum = ::Enumerator.new do |y|
38
+ index.each do |i|
39
+ y << self[i]
40
+ end
41
+ end
42
+ ArrayPromise.new enum
43
+ else
44
+ __force_to__ index
45
+ @array[index]
46
+ end
47
+ else
48
+ enum = ::Enumerator.new do |y|
49
+ i = index
50
+ length.times do
51
+ y << self[i]
52
+ i += 1
53
+ end
54
+ end
55
+ ArrayPromise.new enum
56
+ end
57
+ end
58
+
59
+ def __force_to__ index
60
+ begin
61
+ @mutex.synchronize do
62
+ while @array.length <= index
63
+ @array << @enumerator.next
64
+ end
65
+ end
66
+ rescue ::StopIteration
67
+ end
68
+ end
69
+
70
+ def __forced__?
71
+ if @nil_on_empty && @array.empty?
72
+ # Check if we're really empty.
73
+ __force_to__ 1
74
+ # Either we'll be empty (and finalized as nil), so we'll revert to
75
+ # calling super which forwards to nil and so on,
76
+ # or we'll be nonempty (and normal logic proceeds)
77
+ end
78
+ !(@result.equal?(NOT_SET) && @error.equal?(NOT_SET))
79
+ end
80
+
81
+ # Force all enumerable methods to be defined in terms of laziness.
82
+ %w(
83
+ all? any? chunk collect collect_concat count cycle detect drop
84
+ drop_while each_cons each_entry each_slice each_with_index
85
+ each_with_object entries find find_all find_index first flat_map grep
86
+ group_by include? inject lazy map max max_by member? min min_by minmax
87
+ minmax_by none? one? partition reduce reject reverse_each select
88
+ slice_before sort sort_by take take_while zip
89
+ ).map(&:to_sym).each do |name|
90
+ define_method name do |*args, &block|
91
+ return super if __forced__?
92
+ each.lazy.public_send name, *args, &block
93
+ end
94
+ end
95
+
96
+ %w(length size).map(&:to_sym).each do |name|
97
+ define_method name do
98
+ return super if __forced__?
99
+ each.lazy.count
100
+ end
101
+ end
102
+
103
+ # Except this one, make it a no-op. (Also should prevent infinite recursion.)
104
+ def to_a
105
+ return super if __forced__?
106
+ self
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,44 @@
1
+ module Laziest
2
+ class Counter < Promise
3
+ def initialize enumerator
4
+ @count = 0
5
+ @enumerator = enumerator
6
+ super() do
7
+ ::Kernel.loop do
8
+ @count += enumerator.next
9
+ end
10
+ @count
11
+ end
12
+ end
13
+
14
+ def __force_to__ value
15
+ if @result.equal?(NOT_SET) && @error.equal?(NOT_SET)
16
+ @mutex.synchronize do
17
+ if @result.equal?(NOT_SET) && @error.equal?(NOT_SET)
18
+ begin
19
+ while @count < value
20
+ @count += @enumerator.next
21
+ end
22
+ rescue ::StopIteration
23
+ end
24
+ end
25
+ end
26
+ end
27
+ ::Kernel.raise(@error) unless @error.equal?(NOT_SET)
28
+ end
29
+
30
+ %w(== eql? equal? <= > <=>).map(&:to_sym).each do |op|
31
+ define_method op do |value|
32
+ __force_to__ value+1
33
+ @count.public_send op, value
34
+ end
35
+ end
36
+
37
+ %w(< >=).map(&:to_sym).each do |op|
38
+ define_method op do |value|
39
+ __force_to__ value
40
+ @count.public_send op, value
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,157 @@
1
+ module Laziest
2
+ module Extensions
3
+ module LazyEnumerator
4
+ def count
5
+ enum = Enumerator.new do |y|
6
+ each do |value|
7
+ if (!block_given?) || (yield value)
8
+ y << 1
9
+ end
10
+ end
11
+ end
12
+ Counter.new enum
13
+ end
14
+
15
+ def entries
16
+ ArrayPromise.new enum_for :each
17
+ end
18
+ alias_method :to_a, :entries
19
+
20
+ def group_by &block
21
+ return enum_for :group_by unless block_given?
22
+ Group.new enum_for(:each), &block
23
+ end
24
+
25
+ %w(min max).map(&:to_sym).each do |name|
26
+ klass = MinMax.const_get name.capitalize
27
+ define_method name do
28
+ if block_given?
29
+ # can't handle unnatural ordering
30
+ super
31
+ else
32
+ klass.new enum_for(&:each)
33
+ end
34
+ end
35
+ end
36
+
37
+ # Perfectly functional.
38
+ # TODO: Optimize by sharing an enumerator. As it stands,
39
+ # in the worst case, we enumerate everything twice, defeating the
40
+ # entire point of having a separate 'minmax' method.
41
+ def minmax
42
+ if block_given?
43
+ super
44
+ else
45
+ [min, max]
46
+ end
47
+ end
48
+
49
+ # Another functional-but-stub.
50
+ # TODO: Optimize away the hash, maybe?
51
+ def partition
52
+ return enum_for :partition unless block_given?
53
+ # Negate, forcing these to actual true/false values
54
+ groups = group_by{|*args| ! yield *args}
55
+ # No need to double-negate, the following is just inverted.
56
+ [groups[false], groups[true]]
57
+ end
58
+
59
+ # Implemented mainly in terms of slice_before.
60
+ # It's logically very similar, but has similar amounts of extra sugar.
61
+ def chunk state=nil
62
+ # Handle state, so we can forget about it.
63
+ unless state.nil?
64
+ return chunk {|value| yield value, state}
65
+ end
66
+
67
+ # Even though we could probably do everything with the lazy enum
68
+ # chaining -- and we do _almost_ everything that way -- we want
69
+ # to ensure that first_iteration is reset with each run. A brand
70
+ # new enumerator ensures #rewind and friends work correctly.
71
+
72
+ enum = Enumerator.new do |yield_array|
73
+ prev = nil
74
+ first_iteration = true
75
+
76
+ map {|value|
77
+ [value, (yield value)]
78
+ }.slice_before {|value, result|
79
+ if first_iteration
80
+ first_iteration = false
81
+ # return value doesn't matter
82
+ else
83
+ # start a new group when this one differs from prev, or is
84
+ # specifically requested as 'alone'.
85
+ result != prev || result == :_alone
86
+ end
87
+ prev = result
88
+ }.each do |value, result|
89
+ yield result unless value.nil? || value == :_separator
90
+ end
91
+ end
92
+
93
+ enum.lazy
94
+ end
95
+
96
+ # Chunk without laziness is actually good enough already for most uses.
97
+ # Generally, you're chunking a huge stream into small, manageable chunks.
98
+ # The standard enumerable will stream well enough there -- each chunk
99
+ # must be evaluated completely, but the entire stream is evaluated
100
+ # only as needed. The use case here is if any particular chunk is _very_
101
+ # large, and maybe infinite, but we actually don't need the entire chunk.
102
+ def slice_before state=nil
103
+ unless block_given?
104
+ # state is a pattern
105
+ return slice_before {|value| state === value}
106
+ end
107
+
108
+ unless state.nil?
109
+ # state is state, handle it here
110
+ return slice_before {|value| yield value, state}
111
+ end
112
+
113
+ enum = Enumerator.new do |yield_array|
114
+ ec = enum_for(:each)
115
+ array = nil
116
+ lazy_array = nil
117
+
118
+ begin
119
+ array = [ec.next]
120
+ rescue StopIteration
121
+ array = nil
122
+ end
123
+
124
+ loop do
125
+ break if array.nil?
126
+
127
+ array_promise_enum = Enumerator.new do |yield_element|
128
+ loop do
129
+ value = nil
130
+ begin
131
+ value = ec.next
132
+ if yield value
133
+ array = [value]
134
+ break
135
+ end
136
+ rescue StopIteration
137
+ array = nil
138
+ lazy_array = nil
139
+ break
140
+ end
141
+ yield_element << value
142
+ end
143
+ end
144
+
145
+ lazy_array = ArrayPromise.new array_promise_enum, array
146
+
147
+ yield_array << lazy_array
148
+
149
+ lazy_array.__force__
150
+ end
151
+ end
152
+
153
+ enum.lazy
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,93 @@
1
+ require 'set'
2
+
3
+ module Laziest
4
+ class Group < Promise
5
+ def initialize enumerator, &block
6
+ @hash = {}
7
+ @lazy_arrays = {}
8
+ @buffers = ::Hash.new{|h,k|h[k]=[]}
9
+ @enumerator = enumerator
10
+ @group_block = block
11
+ @enum_mutex = ::Mutex.new
12
+ super() do
13
+ # Any existing lazy arrays can simply be forced, since
14
+ # they're on a different mutex now:
15
+ @lazy_arrays.each do |arr|
16
+ arr.__force__
17
+ end
18
+ # And just in case there aren't any lazy arrays:
19
+ ::Kernel.loop do
20
+ val = enumerator.next
21
+ key = yield val
22
+ (@hash[key] ||= []) << val
23
+ end
24
+ # Strip out anything that turned out to be empty.
25
+ @hash.delete_if {|k,v| v.empty?}
26
+ # clear potentially-expensive GC-able stuff.
27
+ @lazy_arrays = nil
28
+ @buffers = nil
29
+ @group_block = nil
30
+ # safe because we already forced all existing lazy arrays, and we're
31
+ # holding the global mutex which prevents any new ones from being built
32
+ @enum_mutex = nil
33
+ @hash
34
+ end
35
+ end
36
+
37
+ def [] key
38
+ return super unless @result.equal?(NOT_SET) && @error.equal?(NOT_SET)
39
+ @mutex.synchronize do
40
+ @enum_mutex.synchronize do
41
+ # for good measure -- for once, we really _don't_ want to do _any_ of
42
+ # this once the hash's promise resolves.
43
+ return super unless @result.equal?(NOT_SET) && @error.equal?(NOT_SET)
44
+ if @lazy_arrays.has_key?(key)
45
+ @lazy_arrays[key]
46
+ else
47
+ # Note: This enumerator is only ever passed to ArrayPromise.
48
+ # It is not actually a valid enumerator, as it entirely lacks
49
+ # support for rewinding.
50
+ enum = ::Enumerator.new do |yielder|
51
+ @enum_mutex.synchronize do
52
+ buffer = @buffers[key]
53
+ ::Kernel.loop do
54
+ if buffer.empty?
55
+ # At this point, @enumerator.next may throw StopIteration.
56
+ # Which will break the loop, which will end our iteration,
57
+ # which is fine. (A call to our enumerator.each would also
58
+ # result in a StopIteration.)
59
+ v = @enumerator.next
60
+ k = @group_block.call v
61
+ if k == key
62
+ # We shouldn't hold the lock when we yield.
63
+ ::Laziest::MutexUtil.unsynchronize @enum_mutex do
64
+ yielder << v
65
+ end
66
+ elsif @hash.has_key? k
67
+ # Buffer data for the lazy array to read later
68
+ if @lazy_arrays.has_key? k
69
+ @buffers[k] << v
70
+ else
71
+ @hash[k] << v
72
+ end
73
+ else
74
+ # We know a lazy array doesn't exist unless the hash exists
75
+ @hash[k] = [v]
76
+ end
77
+ else
78
+ v = buffer.shift
79
+ ::Laziest::MutexUtil.unsynchronize @enum_mutex do
80
+ yielder << v
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ @hash[key] = [] unless @hash.has_key? key
87
+ @lazy_arrays[key] = ArrayPromise.new enum, @hash[key], true
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,105 @@
1
+ module Laziest
2
+ # Base class. Conceptually a max, but we can override it
3
+ # with min/max classes... but only for naturally-ordered elements!
4
+ # If we allow arbitrary comparators, we have no way of optimizing
5
+ # attempted comparisons against the max value itself.
6
+ class MinMax < Promise
7
+ def initialize enumerator, gtop
8
+ @enumerator = enumerator
9
+ # "Greater-Than" operation
10
+ @gtop = gtop
11
+ begin
12
+ @max = @enumerator.next
13
+ rescue ::StopIteration
14
+ @is_nil = true
15
+ end
16
+
17
+ super() do
18
+ ::Kernel.loop do
19
+ value = @enumerator.next
20
+ if value.public_send @gtop, @max
21
+ @max = value
22
+ end
23
+ end
24
+ @max
25
+ end
26
+ end
27
+
28
+ def __force_until__
29
+ if @result.equal?(NOT_SET) && @error.equal?(NOT_SET)
30
+ @mutex.synchronize do
31
+ if @result.equal?(NOT_SET) && @error.equal?(NOT_SET)
32
+ unless yield
33
+ ::Kernel.loop do
34
+ # StopIteration can stop the loop here
35
+ value = @enumerator.next
36
+ if value.public_send @gtop, @max
37
+ @max = value
38
+ break if yield
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ ::Kernel.raise(@error) unless @error.equal?(NOT_SET)
46
+ end
47
+
48
+ %w(== eql? equal?).map(&:to_sym).each do |name|
49
+ define_method name do |value|
50
+ # If it's _equal_ to our value, it can't be _greater_ than our value.
51
+ # So this can lazily return _false_ if the max is _greater_ than
52
+ # our value. Otherwise, we have to evaluate the whole sequence.
53
+ __force_until__ { @max.public_send @gtop, value }
54
+ @max.public_send name, value
55
+ end
56
+ end
57
+
58
+ # The individual Min and Max classes provide additional optimizations for
59
+ # naturally-ordered elements.
60
+ class Max < MinMax
61
+ def initialize enumerator
62
+ super(enumerator, :>)
63
+ end
64
+
65
+ %w(< >=).map(&:to_sym).each do |name|
66
+ define_method name do |value|
67
+ # If we find a max at least as large as value, then < is false,
68
+ # and >= is true. (Otherwise, we must enumerate everything.)
69
+ __force_until__ { @max >= value }
70
+ @max.public_send name, value
71
+ end
72
+ end
73
+
74
+ %w(<= <=> >).map(&:to_sym).each do |name|
75
+ define_method name do |value|
76
+ # If we find a max _larger_ than a value, then <= is false,
77
+ # > is true, and <=> can be evaluated directly.
78
+ __force_until__ { @max > value }
79
+ @max.public_send name, value
80
+ end
81
+ end
82
+ end
83
+
84
+ # Inverse of above.
85
+ class Min < MinMax
86
+ def initialize enumerator
87
+ super(enumerator, :<)
88
+ end
89
+
90
+ %w(> <=).map(&:to_sym).each do |name|
91
+ define_method name do |value|
92
+ __force_until__ { @max <= value }
93
+ @max.public_send name, value
94
+ end
95
+ end
96
+
97
+ %w(>= <=> <).map(&:to_sym).each do |name|
98
+ define_method name do |value|
99
+ __force_until__ { @max < value }
100
+ @max.public_send name, value
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,13 @@
1
+ module Laziest
2
+ module MutexUtil
3
+ # opposite of Mutex#synchronize.
4
+ def self.unsynchronize mutex
5
+ mutex.unlock
6
+ begin
7
+ yield
8
+ ensure
9
+ mutex.lock
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Laziest
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: laziest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Masover
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-10 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.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: promise
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: soft_reference
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: The laziest possible enumerables and enumerators
70
+ email:
71
+ - masover@iastate.edu
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - laziest.gemspec
82
+ - lib/laziest.rb
83
+ - lib/laziest/array.rb
84
+ - lib/laziest/counter.rb
85
+ - lib/laziest/extensions.rb
86
+ - lib/laziest/group.rb
87
+ - lib/laziest/minmax.rb
88
+ - lib/laziest/mutex_util.rb
89
+ - lib/laziest/version.rb
90
+ homepage: ''
91
+ licenses:
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.0.3
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: When there's just no O(1) way to compute something, this gem provides both
114
+ promises (lazy evaluation) and partial evaluation, along with implicit, softref-based
115
+ memoization. For example, (foo.lazy.count > 5) will invoke the iterator at most
116
+ six times.
117
+ test_files: []