1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
unless Enumerable.method_defined? :lazy
require 'backports/tools/arguments'
require 'backports/1.9.1/enumerator/new'
module Enumerable
def lazy
klass = Enumerator::Lazy.send :class_variable_get, :@@lazy_with_no_block # Note: class_variable_get is private in 1.8
Enumerator::Lazy.new(klass.new(self, :each, []))
end
end
class Enumerator
class Yielder
# Current API for Lazy Enumerator does not provide an easy way
# to handle internal state. We "cheat" and use yielder to hold it for us.
# A new yielder is created when generating or after a `rewind`.
# This way we avoid issues like http://bugs.ruby-lang.org/issues/7691
# or http://bugs.ruby-lang.org/issues/7696
attr_accessor :backports_memo
end
class Lazy < Enumerator
@@done = :__backports_lazy_enumeration_done__ # used internally to bail out of an iteration
@@lazy_with_no_block = Struct.new(:object, :method, :args) # used internally to create lazy without block
def initialize(obj)
if obj.is_a?(@@lazy_with_no_block)
@inspect_info = obj
return super(@receiver = obj.object, @method = obj.method || :each, * @args = obj.args)
end
raise ArgumentError, "must supply a block" unless block_given?
@receiver = obj
super() do |yielder, *args|
catch @@done do
obj.each(*args) do |*x|
yield yielder, *x
end
end
end
end
alias_method :force, :to_a
def lazy
self
end
def to_enum(method = :each, *args)
Lazy.new(@@lazy_with_no_block.new(self, method, args))
end
alias_method :enum_for, :to_enum
def inspect
suff = ''
suff << ":#{@method}" unless @method.nil? || @method == :each
suff << "(#{@args.inspect[1...-1]})" if @args && !@args.empty?
"#<#{self.class}: #{@receiver.inspect}#{suff}>"
end
{
:slice_before => //,
:with_index => [],
:cycle => [],
:each_with_object => 42,
:each_slice => 42,
:each_entry => [],
:each_cons => 42,
}.each do |method, args|
next unless Enumerator.method_defined? method
unless [].lazy.send(method, *args).is_a?(Lazy) # Nothing to do if already backported, since it would use to_enum...
module_eval <<-EOT, __FILE__, __LINE__ + 1
def #{method}(*args) # def cycle(*args)
return to_enum(:#{method}, *args) unless block_given? # return to_enum(:cycle, *args) unless block_given?
super # super
end # end
EOT
end
end
def chunk(*)
super.lazy
end if Enumerable.method_defined?(:chunk) && ![].lazy.chunk{}.is_a?(Lazy)
def map
raise ArgumentError, "tried to call lazy map without a block" unless block_given?
Lazy.new(self) do |yielder, *values|
yielder << yield(*values)
end.__set_inspect :map
end
alias_method :collect, :map
def select
raise ArgumentError, "tried to call lazy select without a block" unless block_given?
Lazy.new(self) do |yielder, *values|
values = values.first unless values.size > 1
yielder.yield values if yield values
end.__set_inspect :select
end
alias_method :find_all, :select
def reject
raise ArgumentError, "tried to call lazy reject without a block" unless block_given?
Lazy.new(self) do |yielder, *values|
values = values.first unless values.size > 1
yielder.yield(values) unless yield values
end.__set_inspect :reject
end
def grep(pattern)
if block_given?
# Split for performance
Lazy.new(self) do |yielder, *values|
values = values.first unless values.size > 1
yielder.yield(yield(values)) if pattern === values
end
else
Lazy.new(self) do |yielder, *values|
values = values.first unless values.size > 1
yielder.yield(values) if pattern === values
end
end.__set_inspect :grep, [pattern]
end
def drop(n)
n = Backports::coerce_to_int(n)
Lazy.new(self) do |yielder, *values|
data = yielder.backports_memo ||= {:remain => n}
if data[:remain] > 0
data[:remain] -= 1
else
yielder.yield(*values)
end
end.__set_inspect :drop, [n]
end
def drop_while
raise ArgumentError, "tried to call lazy drop_while without a block" unless block_given?
Lazy.new(self) do |yielder, *values|
data = yielder.backports_memo ||= {:dropping => true}
yielder.yield(*values) unless data[:dropping] &&= yield(*values)
end.__set_inspect :drop_while
end
def take(n)
n = Backports::coerce_to_int(n)
raise ArgumentError, 'attempt to take negative size' if n < 0
Lazy.new(n == 0 ? [] : self) do |yielder, *values|
data = yielder.backports_memo ||= {:remain => n}
yielder.yield(*values)
throw @@done if (data[:remain] -= 1) == 0
end.__set_inspect :take, [n], self
end
def take_while
raise ArgumentError, "tried to call lazy take_while without a block" unless block_given?
Lazy.new(self) do |yielder, *values|
throw @@done unless yield(*values)
yielder.yield(*values)
end.__set_inspect :take_while
end
def flat_map
raise ArgumentError, "tried to call lazy flat_map without a block" unless block_given?
Lazy.new(self) do |yielder, *values|
result = yield(*values)
ary = Backports.is_array?(result)
if ary || (result.respond_to?(:each) && result.respond_to?(:force))
(ary || result).each{|x| yielder << x }
else
yielder << result
end
end.__set_inspect :flat_map
end
alias_method :collect_concat, :flat_map
def zip(*args)
return super if block_given?
arys = args.map{ |arg| Backports.is_array?(arg) }
if arys.all?
# Handle trivial case of multiple array arguments separately
# by avoiding Enumerator#next for efficiency & compatibility
Lazy.new(self) do |yielder, *values|
data = yielder.backports_memo ||= {:iter => 0}
values = values.first unless values.size > 1
yielder << arys.map{|ary| ary[data[:iter]]}.unshift(values)
data[:iter] += 1
end
else
args.each do |a|
raise TypeError, "wrong argument type #{a.class} (must respond to :each)" unless a.respond_to? :each
end
Lazy.new(self) do |yielder, *values|
enums = yielder.backports_memo ||= args.map(&:to_enum)
values = values.first unless values.size > 1
others = enums.map do |arg|
begin
arg.next
rescue StopIteration
nil
end
end
yielder << others.unshift(values)
end
end.__set_inspect :zip, args
end
protected
def __set_inspect(method, args = nil, receiver = nil)
@method = method
@args = args
@receiver = receiver if receiver
self
end
end
end
end
|