async-enumerable 0.1.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/.rspec +3 -0
- data/.standard.yml +5 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +416 -0
- data/Rakefile +127 -0
- data/benchmark/async_all.yaml +38 -0
- data/benchmark/async_any.yaml +39 -0
- data/benchmark/async_each.yaml +51 -0
- data/benchmark/async_find.yaml +37 -0
- data/benchmark/async_map.yaml +50 -0
- data/benchmark/async_select.yaml +31 -0
- data/benchmark/early_termination/any_early.yaml +17 -0
- data/benchmark/early_termination/any_late.yaml +17 -0
- data/benchmark/early_termination/find_middle.yaml +17 -0
- data/benchmark/size_comparison/map_10.yaml +17 -0
- data/benchmark/size_comparison/map_100.yaml +17 -0
- data/benchmark/size_comparison/map_1000.yaml +20 -0
- data/benchmark/size_comparison/map_10000.yaml +23 -0
- data/docs/reference/README.md +43 -0
- data/docs/reference/concurrency_bounder.md +234 -0
- data/docs/reference/enumerable.md +258 -0
- data/docs/reference/enumerator.md +221 -0
- data/docs/reference/methods/converters.md +97 -0
- data/docs/reference/methods/predicates.md +254 -0
- data/docs/reference/methods/transformers.md +104 -0
- data/lib/async/enumerable/comparable.rb +26 -0
- data/lib/async/enumerable/concurrency_bounder.rb +37 -0
- data/lib/async/enumerable/configurable.rb +140 -0
- data/lib/async/enumerable/methods/aggregators.rb +40 -0
- data/lib/async/enumerable/methods/converters.rb +21 -0
- data/lib/async/enumerable/methods/each.rb +39 -0
- data/lib/async/enumerable/methods/iterators.rb +27 -0
- data/lib/async/enumerable/methods/predicates/all.rb +47 -0
- data/lib/async/enumerable/methods/predicates/any.rb +47 -0
- data/lib/async/enumerable/methods/predicates/find.rb +55 -0
- data/lib/async/enumerable/methods/predicates/find_index.rb +50 -0
- data/lib/async/enumerable/methods/predicates/include.rb +23 -0
- data/lib/async/enumerable/methods/predicates/none.rb +27 -0
- data/lib/async/enumerable/methods/predicates/one.rb +48 -0
- data/lib/async/enumerable/methods/predicates.rb +29 -0
- data/lib/async/enumerable/methods/slicers.rb +34 -0
- data/lib/async/enumerable/methods/transformers/compact.rb +18 -0
- data/lib/async/enumerable/methods/transformers/filter_map.rb +19 -0
- data/lib/async/enumerable/methods/transformers/flat_map.rb +20 -0
- data/lib/async/enumerable/methods/transformers/map.rb +22 -0
- data/lib/async/enumerable/methods/transformers/reject.rb +19 -0
- data/lib/async/enumerable/methods/transformers/select.rb +21 -0
- data/lib/async/enumerable/methods/transformers/sort.rb +18 -0
- data/lib/async/enumerable/methods/transformers/sort_by.rb +19 -0
- data/lib/async/enumerable/methods/transformers/uniq.rb +18 -0
- data/lib/async/enumerable/methods/transformers.rb +35 -0
- data/lib/async/enumerable/methods.rb +26 -0
- data/lib/async/enumerable/version.rb +10 -0
- data/lib/async/enumerable.rb +72 -0
- data/lib/async/enumerator.rb +33 -0
- data/lib/enumerable/async.rb +38 -0
- data/scripts/debug_config.rb +26 -0
- data/scripts/debug_config2.rb +34 -0
- data/scripts/sketch.rb +30 -0
- data/scripts/test_aggregators.rb +66 -0
- data/scripts/test_ancestors.rb +12 -0
- data/scripts/test_async_chaining.rb +30 -0
- data/scripts/test_direct_method_calls.rb +53 -0
- data/scripts/test_example.rb +37 -0
- data/scripts/test_issue_7.rb +69 -0
- data/scripts/test_method_source.rb +15 -0
- metadata +145 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "async/enumerable/methods/each"
|
4
|
+
|
5
|
+
require "async/enumerable/methods/aggregators"
|
6
|
+
require "async/enumerable/methods/converters"
|
7
|
+
require "async/enumerable/methods/iterators"
|
8
|
+
require "async/enumerable/methods/predicates"
|
9
|
+
require "async/enumerable/methods/slicers"
|
10
|
+
require "async/enumerable/methods/transformers"
|
11
|
+
|
12
|
+
module Async
|
13
|
+
module Enumerable
|
14
|
+
# Methods contains all async implementations of Enumerable methods
|
15
|
+
module Methods
|
16
|
+
include Each
|
17
|
+
|
18
|
+
include Aggregators
|
19
|
+
include Converters
|
20
|
+
include Iterators
|
21
|
+
include Predicates
|
22
|
+
include Slicers
|
23
|
+
include Transformers
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "async"
|
4
|
+
require "async/barrier"
|
5
|
+
require "async/semaphore"
|
6
|
+
|
7
|
+
require "concurrent/array"
|
8
|
+
require "concurrent/atomic/atomic_boolean"
|
9
|
+
require "concurrent/atomic/atomic_fixnum"
|
10
|
+
require "concurrent/atomic/atomic_reference"
|
11
|
+
|
12
|
+
require "forwardable"
|
13
|
+
|
14
|
+
module Async
|
15
|
+
module Enumerable
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require "async/enumerable/configurable"
|
20
|
+
require "async/enumerable/concurrency_bounder"
|
21
|
+
require "async/enumerable/comparable"
|
22
|
+
require "async/enumerable/methods"
|
23
|
+
require "async/enumerable/version"
|
24
|
+
|
25
|
+
module Async
|
26
|
+
# Provides async parallel execution for Enumerable.
|
27
|
+
# See docs/reference/enumerable.md for detailed documentation.
|
28
|
+
module Enumerable
|
29
|
+
@__async_enumerable_config_ref = Concurrent::AtomicReference.new(Configurable::Config.new)
|
30
|
+
extend Configurable
|
31
|
+
include Configurable
|
32
|
+
|
33
|
+
class << self
|
34
|
+
alias_method :config, :__async_enumerable_config
|
35
|
+
alias_method :configure, :__async_enumerable_config
|
36
|
+
|
37
|
+
# Configures modules when included in a class.
|
38
|
+
# @param base [Class] The including class
|
39
|
+
def included(base)
|
40
|
+
base.extend(Configurable)
|
41
|
+
base.extend(Configurable::ClassMethods)
|
42
|
+
base.include(Configurable)
|
43
|
+
base.include(Comparable)
|
44
|
+
base.include(Methods)
|
45
|
+
base.include(AsyncMethod)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Gets the module-level config reference.
|
49
|
+
# @return [AtomicReference] Config reference
|
50
|
+
def config_ref
|
51
|
+
@__async_enumerable_config_ref
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Async method module - included last to override Enumerable's version.
|
56
|
+
module AsyncMethod
|
57
|
+
# Returns async enumerator (idempotent - returns self if already async).
|
58
|
+
# @param kwargs [Hash] Configuration options (max_fibers, etc.)
|
59
|
+
# @return [Async::Enumerator] Async wrapper
|
60
|
+
def async(**kwargs)
|
61
|
+
return self if kwargs.empty? && self.class.include?(Async::Enumerable)
|
62
|
+
|
63
|
+
source = __async_enumerable_collection || self
|
64
|
+
config = __async_enumerable_config
|
65
|
+
Async::Enumerator.new(source, config, **kwargs)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
require "async/enumerator"
|
72
|
+
require "enumerable/async"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Async
|
4
|
+
# Wrapper providing async enumerable methods for parallel execution.
|
5
|
+
# See docs/reference/enumerator.md for detailed documentation.
|
6
|
+
class Enumerator
|
7
|
+
include Async::Enumerable
|
8
|
+
def_async_enumerable :@enumerable
|
9
|
+
|
10
|
+
# Creates async wrapper for enumerable.
|
11
|
+
# @param enumerable [Enumerable] Object to wrap
|
12
|
+
# @param config [Config, nil] Configuration object
|
13
|
+
# @param kwargs [Hash] Configuration options (max_fibers, etc.)
|
14
|
+
def initialize(enumerable = [], config = nil, **kwargs)
|
15
|
+
@enumerable = enumerable
|
16
|
+
|
17
|
+
# Only configure if config or kwargs are provided
|
18
|
+
if config || !kwargs.empty?
|
19
|
+
__async_enumerable_configure do |cfg|
|
20
|
+
# Merge config if provided
|
21
|
+
config&.to_h&.each do |key, val|
|
22
|
+
cfg[key] = val if val
|
23
|
+
end
|
24
|
+
|
25
|
+
# Merge kwargs if provided
|
26
|
+
kwargs.each do |key, val|
|
27
|
+
cfg[key] = val if val
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extension to Ruby's Enumerable module that adds asynchronous capabilities.
|
4
|
+
module Enumerable
|
5
|
+
# Returns an AsyncEnumerator wrapper that provides asynchronous versions of
|
6
|
+
# Enumerable methods for parallel execution.
|
7
|
+
#
|
8
|
+
# This method enables concurrent processing of Enumerable operations using
|
9
|
+
# the `socketry/async` library, allowing for significant performance
|
10
|
+
# improvements when dealing with I/O-bound operations or large collections.
|
11
|
+
#
|
12
|
+
# @param kwargs [Hash] Configuration options (max_fibers, etc.)
|
13
|
+
#
|
14
|
+
# @example Basic usage with async map
|
15
|
+
# [1, 2, 3].async.map { |n| n * 2 } # => [2, 4, 6] (executed in parallel)
|
16
|
+
#
|
17
|
+
# @example Using with I/O operations
|
18
|
+
# urls.async.map { |url| fetch_data(url) } # Fetches all URLs concurrently
|
19
|
+
#
|
20
|
+
# @example With custom fiber limit
|
21
|
+
# huge_dataset.async(max_fibers: 100).map { |item| process(item) }
|
22
|
+
# # Processes with max 100 concurrent fibers
|
23
|
+
#
|
24
|
+
# @example Chaining async operations
|
25
|
+
# data.async
|
26
|
+
# .select { |item| item.valid? }
|
27
|
+
# .map { |item| process(item) }
|
28
|
+
# .to_a
|
29
|
+
#
|
30
|
+
# @return [Async::Enumerator] An async wrapper around this
|
31
|
+
# enumerable that provides parallel execution capabilities for enumerable
|
32
|
+
# methods
|
33
|
+
#
|
34
|
+
# @see Async::Enumerator
|
35
|
+
def async(**kwargs)
|
36
|
+
Async::Enumerator.new(self, **kwargs)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative "../lib/async/enumerable"
|
3
|
+
|
4
|
+
# Test 1: Module config
|
5
|
+
puts "=== Test 1: Module config ==="
|
6
|
+
puts "Before configure: #{Async::Enumerable.config.max_fibers}"
|
7
|
+
Async::Enumerable.configure { |c| c.max_fibers = 50 }
|
8
|
+
puts "After configure: #{Async::Enumerable.config.max_fibers}"
|
9
|
+
|
10
|
+
# Test 2: Create enumerator with no config
|
11
|
+
puts "\n=== Test 2: Enumerator with no config ==="
|
12
|
+
enum = Async::Enumerator.new([1, 2, 3])
|
13
|
+
puts "Enumerator config: #{enum.__async_enumerable_config.max_fibers}"
|
14
|
+
puts "Module config: #{Async::Enumerable.config.max_fibers}"
|
15
|
+
|
16
|
+
# Test 3: Check config_ref
|
17
|
+
puts "\n=== Test 3: Config ref details ==="
|
18
|
+
puts "Enumerator config_ref: #{enum.__async_enumerable_config_ref}"
|
19
|
+
puts "Enumerator config_ref.get: #{enum.__async_enumerable_config_ref.get}"
|
20
|
+
puts "Module config_ref: #{Async::Enumerable.config_ref}"
|
21
|
+
puts "Module config_ref.get: #{Async::Enumerable.config_ref.get}"
|
22
|
+
|
23
|
+
# Test 4: Check merge_all_config
|
24
|
+
puts "\n=== Test 4: Merge all config ==="
|
25
|
+
merged = enum.__async_enumerable_merge_all_config
|
26
|
+
puts "Merged config: #{merged}"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative "../lib/async/enumerable"
|
3
|
+
|
4
|
+
# Check initial state
|
5
|
+
puts "=== Initial state ==="
|
6
|
+
puts "Module config: #{Async::Enumerable.config.max_fibers}"
|
7
|
+
puts "Enumerator class config: #{Async::Enumerator.__async_enumerable_config.max_fibers if Async::Enumerator.respond_to?(:__async_enumerable_config)}"
|
8
|
+
|
9
|
+
# Now configure module
|
10
|
+
puts "\n=== After configuring module ==="
|
11
|
+
Async::Enumerable.configure { |c| c.max_fibers = 50 }
|
12
|
+
puts "Module config: #{Async::Enumerable.config.max_fibers}"
|
13
|
+
puts "Enumerator class config: #{Async::Enumerator.__async_enumerable_config.max_fibers if Async::Enumerator.respond_to?(:__async_enumerable_config)}"
|
14
|
+
|
15
|
+
# Create a fresh class that includes Async::Enumerable
|
16
|
+
puts "\n=== Creating fresh class after module config ==="
|
17
|
+
class TestClass
|
18
|
+
include Async::Enumerable
|
19
|
+
def_async_enumerable :@data
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@data = [1, 2, 3]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
puts "TestClass config: #{TestClass.__async_enumerable_config.max_fibers}"
|
27
|
+
|
28
|
+
# Create instance
|
29
|
+
puts "\n=== Creating instances ==="
|
30
|
+
enum = Async::Enumerator.new([1, 2, 3])
|
31
|
+
puts "Enumerator instance config: #{enum.__async_enumerable_config.max_fibers}"
|
32
|
+
|
33
|
+
test = TestClass.new
|
34
|
+
puts "TestClass instance config: #{test.__async_enumerable_config.max_fibers}"
|
data/scripts/sketch.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# i'm flailing a little bit here. what am i going for?
|
2
|
+
|
3
|
+
class Foo
|
4
|
+
include Async::Enumerable
|
5
|
+
def_async_enumerable :@bar, max_fibers: 100, pipeline: true
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@bar = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform
|
12
|
+
Sync do |parent|
|
13
|
+
# @bar = [1, 2, 3, 4]
|
14
|
+
async(parent:) # update the instance config to use this parent
|
15
|
+
.map { |x| x + 1 } # async map over @bar, returning a task that adds 1 for each
|
16
|
+
# => Task{1 + 1}, Task{2 + 1}, Task{3 + 1}, Task{4 + 1} # unordered, individual
|
17
|
+
.accumulate(2, timeout_ms: 200) # gather batches up to 2 waiting up to 200ms before sending
|
18
|
+
# => [Task{1 + 1}, Task{2 + 1}], [Task{3 + 1}, Task{4 + 1}] # unordered
|
19
|
+
.map { |x| x.wait.sum }
|
20
|
+
# x is an accumulated Async::Enumerator<Task>
|
21
|
+
# wait blocks until all included tasks are resolved, then returns self
|
22
|
+
# sum is an Enumerable method called on the Async::Enumerator
|
23
|
+
# => [???]
|
24
|
+
.flatten
|
25
|
+
# => [Task...]
|
26
|
+
.sync # resolve the entire pipeline and all tasks in it
|
27
|
+
# => [2, 3, 4, 5]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "async"
|
6
|
+
require "async/enumerable"
|
7
|
+
|
8
|
+
# Create a class that tracks when #each is called
|
9
|
+
class TrackedArray
|
10
|
+
include Async::Enumerable
|
11
|
+
def_enumerator :items
|
12
|
+
|
13
|
+
attr_reader :items, :each_called
|
14
|
+
|
15
|
+
def initialize(items)
|
16
|
+
@items = items
|
17
|
+
@each_called = false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Sync do
|
22
|
+
puts "Testing aggregator methods to verify they use async #each:"
|
23
|
+
puts
|
24
|
+
|
25
|
+
# Test reduce
|
26
|
+
tracked = TrackedArray.new([1, 2, 3, 4, 5])
|
27
|
+
result = tracked.async.reduce(0) do |sum, n|
|
28
|
+
puts " reduce: processing #{n} in fiber"
|
29
|
+
sum + n
|
30
|
+
end
|
31
|
+
puts "reduce result: #{result}"
|
32
|
+
puts
|
33
|
+
|
34
|
+
# Test sum with block
|
35
|
+
tracked = TrackedArray.new([1, 2, 3, 4, 5])
|
36
|
+
result = tracked.async.sum do |n|
|
37
|
+
puts " sum: processing #{n} in fiber"
|
38
|
+
n * 2
|
39
|
+
end
|
40
|
+
puts "sum result: #{result}"
|
41
|
+
puts
|
42
|
+
|
43
|
+
# Test count with block
|
44
|
+
tracked = TrackedArray.new([1, 2, 3, 4, 5])
|
45
|
+
result = tracked.async.count do |n|
|
46
|
+
puts " count: checking #{n} in fiber"
|
47
|
+
n.even?
|
48
|
+
end
|
49
|
+
puts "count result: #{result}"
|
50
|
+
puts
|
51
|
+
|
52
|
+
# Test tally
|
53
|
+
tracked = TrackedArray.new(%w[a b a c b a])
|
54
|
+
result = tracked.async.tally
|
55
|
+
puts "tally result: #{result}"
|
56
|
+
puts
|
57
|
+
|
58
|
+
# Test min/max with expensive comparison
|
59
|
+
tracked = TrackedArray.new([5, 2, 8, 1, 9])
|
60
|
+
result = tracked.async.min_by do |n|
|
61
|
+
puts " min_by: evaluating #{n} in fiber"
|
62
|
+
sleep(0.1) # Simulate expensive computation
|
63
|
+
n * -1 # Find the max by negating
|
64
|
+
end
|
65
|
+
puts "min_by result: #{result}"
|
66
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/async/enumerable"
|
5
|
+
|
6
|
+
e = [1, 2, 3].async
|
7
|
+
puts "Async::Enumerator ancestors:"
|
8
|
+
puts e.class.ancestors.take(10).map(&:to_s).join("\n ")
|
9
|
+
|
10
|
+
puts "\nMethod resolution for async:"
|
11
|
+
puts "Method owner: #{e.method(:async).owner}"
|
12
|
+
puts "Source: #{e.method(:async).source_location.inspect}"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/async/enumerable"
|
5
|
+
|
6
|
+
puts "Testing async chaining..."
|
7
|
+
e = [1, 2, 3].async
|
8
|
+
puts "First async: #{e.class}"
|
9
|
+
puts "Is Async::Enumerator? #{e.is_a?(Async::Enumerator)}"
|
10
|
+
|
11
|
+
# Add debug to the async method temporarily
|
12
|
+
class Async::Enumerator
|
13
|
+
def async_debug(max_fibers: nil)
|
14
|
+
puts " In async method"
|
15
|
+
puts " self.class: #{self.class}"
|
16
|
+
puts " is_a?(::Async::Enumerator): #{is_a?(::Async::Enumerator)}"
|
17
|
+
|
18
|
+
if is_a?(::Async::Enumerator)
|
19
|
+
puts " Returning self!"
|
20
|
+
return self
|
21
|
+
end
|
22
|
+
|
23
|
+
puts " Creating new enumerator..."
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
e2 = e.async_debug
|
29
|
+
puts "Second async: #{e2.class}"
|
30
|
+
puts "Same object? #{e.equal?(e2)}"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Test script to demonstrate issue #7 - methods referencing @enumerable directly
|
3
|
+
|
4
|
+
require_relative "../lib/async/enumerable"
|
5
|
+
|
6
|
+
class MyCollection
|
7
|
+
include Async::Enumerable
|
8
|
+
def_enumerator :items
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@items = [1, 2, 3, 4, 5]
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :items
|
15
|
+
end
|
16
|
+
|
17
|
+
collection = MyCollection.new
|
18
|
+
puts "Collection responds to any?: #{collection.respond_to?(:any?)}"
|
19
|
+
puts "Collection.async responds to any?: #{collection.async.respond_to?(:any?)}"
|
20
|
+
|
21
|
+
# Try calling any? directly on the collection (not through async)
|
22
|
+
begin
|
23
|
+
result = collection.any? { |x| x > 3 }
|
24
|
+
puts "Direct any? result: #{result}"
|
25
|
+
rescue => e
|
26
|
+
puts "Direct any? error: #{e.message}"
|
27
|
+
puts " Backtrace: #{e.backtrace.first(3).join("\n ")}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Try calling through async (should work)
|
31
|
+
begin
|
32
|
+
result = collection.async.any? { |x| x > 3 }
|
33
|
+
puts "Async any? result: #{result}"
|
34
|
+
rescue => e
|
35
|
+
puts "Async any? error: #{e.message}"
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "\n--- Testing all? method ---"
|
39
|
+
# Try all? directly
|
40
|
+
begin
|
41
|
+
result = collection.all? { |x| x > 0 }
|
42
|
+
puts "Direct all? result: #{result}"
|
43
|
+
rescue => e
|
44
|
+
puts "Direct all? error: #{e.message}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Try all? through async
|
48
|
+
begin
|
49
|
+
result = collection.async.all? { |x| x > 0 }
|
50
|
+
puts "Async all? result: #{result}"
|
51
|
+
rescue => e
|
52
|
+
puts "Async all? error: #{e.message}"
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "lib/async/enumerable"
|
5
|
+
|
6
|
+
# Example class that includes Async::Enumerable
|
7
|
+
class TodoList
|
8
|
+
include Async::Enumerable
|
9
|
+
def_enumerator :todos
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@todos = []
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :todos
|
16
|
+
|
17
|
+
def add(todo)
|
18
|
+
@todos << todo
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Test the new functionality
|
23
|
+
list = TodoList.new
|
24
|
+
list.add("Buy milk")
|
25
|
+
list.add("Write code")
|
26
|
+
list.add("Review PR")
|
27
|
+
|
28
|
+
puts "Testing def_enumerator with TodoList:"
|
29
|
+
results = list.async.map { |todo| "✓ #{todo}" }.sync
|
30
|
+
puts results.inspect
|
31
|
+
|
32
|
+
# Test that regular arrays still work
|
33
|
+
puts "\nTesting regular array async:"
|
34
|
+
array_results = [1, 2, 3].async.map { |n| n * 2 }.sync
|
35
|
+
puts array_results.inspect
|
36
|
+
|
37
|
+
puts "\nAll tests passed!"
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Test script to verify issue #7 is resolved
|
5
|
+
# Issue: Methods should use enumerable source from def_enumerator, not @enumerable directly
|
6
|
+
|
7
|
+
require_relative "../lib/async/enumerable"
|
8
|
+
|
9
|
+
# Test 1: Includable pattern with def_enumerator
|
10
|
+
class MyCollection
|
11
|
+
include Async::Enumerable
|
12
|
+
def_enumerator :items # Specifies that 'items' method returns the enumerable
|
13
|
+
|
14
|
+
def initialize(data)
|
15
|
+
@items = data
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :items
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "Test 1: Includable pattern with def_enumerator"
|
22
|
+
collection = MyCollection.new([1, 2, 3, 4, 5])
|
23
|
+
|
24
|
+
# Test async operations work with the specified source
|
25
|
+
result = collection.async.map { |x| x * 2 }.to_a
|
26
|
+
puts " map result: #{result.inspect}"
|
27
|
+
puts " ✓ map works" if result == [2, 4, 6, 8, 10]
|
28
|
+
|
29
|
+
# Test predicate methods work
|
30
|
+
all_positive = collection.all? { |x| x > 0 }
|
31
|
+
puts " all? result: #{all_positive}"
|
32
|
+
puts " ✓ all? works" if all_positive == true
|
33
|
+
|
34
|
+
any_large = collection.any? { |x| x > 3 }
|
35
|
+
puts " any? result: #{any_large}"
|
36
|
+
puts " ✓ any? works" if any_large == true
|
37
|
+
|
38
|
+
# Test 2: Async::Enumerator with @enumerable instance variable
|
39
|
+
puts "\nTest 2: Async::Enumerator wrapper pattern"
|
40
|
+
async_enum = [1, 2, 3, 4, 5].async
|
41
|
+
|
42
|
+
# Verify it uses @enumerable correctly
|
43
|
+
result = async_enum.map { |x| x * 2 }.to_a
|
44
|
+
puts " map result: #{result.inspect}"
|
45
|
+
puts " ✓ Async::Enumerator map works" if result == [2, 4, 6, 8, 10]
|
46
|
+
|
47
|
+
# Test 3: Class without def_enumerator (uses self)
|
48
|
+
class DirectCollection
|
49
|
+
include Async::Enumerable
|
50
|
+
include Enumerable
|
51
|
+
|
52
|
+
def initialize(data)
|
53
|
+
@data = data
|
54
|
+
end
|
55
|
+
|
56
|
+
def each(&block)
|
57
|
+
@data.each(&block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "\nTest 3: Without def_enumerator (uses self)"
|
62
|
+
direct = DirectCollection.new([1, 2, 3, 4, 5])
|
63
|
+
|
64
|
+
# Should use self as the enumerable source
|
65
|
+
result = direct.async.map { |x| x * 2 }.to_a
|
66
|
+
puts " map result: #{result.inspect}"
|
67
|
+
puts " ✓ Direct collection works" if result == [2, 4, 6, 8, 10]
|
68
|
+
|
69
|
+
puts "\n✅ Issue #7 is RESOLVED! All methods properly use the enumerable source."
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/async/enumerable"
|
5
|
+
|
6
|
+
e = [1, 2, 3].async
|
7
|
+
puts "First async: #{e.class}"
|
8
|
+
|
9
|
+
# Check which async method is defined
|
10
|
+
puts "\nMethod owner: #{e.method(:async).owner}"
|
11
|
+
puts "Method source location: #{e.method(:async).source_location.inspect}"
|
12
|
+
|
13
|
+
# Check class methods
|
14
|
+
puts "\nClass responds to enumerable_source? #{e.class.respond_to?(:enumerable_source)}"
|
15
|
+
puts "Enumerable source: #{e.class.enumerable_source.inspect}" if e.class.respond_to?(:enumerable_source)
|