laziest 0.0.1

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 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: []