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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +5 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +416 -0
  8. data/Rakefile +127 -0
  9. data/benchmark/async_all.yaml +38 -0
  10. data/benchmark/async_any.yaml +39 -0
  11. data/benchmark/async_each.yaml +51 -0
  12. data/benchmark/async_find.yaml +37 -0
  13. data/benchmark/async_map.yaml +50 -0
  14. data/benchmark/async_select.yaml +31 -0
  15. data/benchmark/early_termination/any_early.yaml +17 -0
  16. data/benchmark/early_termination/any_late.yaml +17 -0
  17. data/benchmark/early_termination/find_middle.yaml +17 -0
  18. data/benchmark/size_comparison/map_10.yaml +17 -0
  19. data/benchmark/size_comparison/map_100.yaml +17 -0
  20. data/benchmark/size_comparison/map_1000.yaml +20 -0
  21. data/benchmark/size_comparison/map_10000.yaml +23 -0
  22. data/docs/reference/README.md +43 -0
  23. data/docs/reference/concurrency_bounder.md +234 -0
  24. data/docs/reference/enumerable.md +258 -0
  25. data/docs/reference/enumerator.md +221 -0
  26. data/docs/reference/methods/converters.md +97 -0
  27. data/docs/reference/methods/predicates.md +254 -0
  28. data/docs/reference/methods/transformers.md +104 -0
  29. data/lib/async/enumerable/comparable.rb +26 -0
  30. data/lib/async/enumerable/concurrency_bounder.rb +37 -0
  31. data/lib/async/enumerable/configurable.rb +140 -0
  32. data/lib/async/enumerable/methods/aggregators.rb +40 -0
  33. data/lib/async/enumerable/methods/converters.rb +21 -0
  34. data/lib/async/enumerable/methods/each.rb +39 -0
  35. data/lib/async/enumerable/methods/iterators.rb +27 -0
  36. data/lib/async/enumerable/methods/predicates/all.rb +47 -0
  37. data/lib/async/enumerable/methods/predicates/any.rb +47 -0
  38. data/lib/async/enumerable/methods/predicates/find.rb +55 -0
  39. data/lib/async/enumerable/methods/predicates/find_index.rb +50 -0
  40. data/lib/async/enumerable/methods/predicates/include.rb +23 -0
  41. data/lib/async/enumerable/methods/predicates/none.rb +27 -0
  42. data/lib/async/enumerable/methods/predicates/one.rb +48 -0
  43. data/lib/async/enumerable/methods/predicates.rb +29 -0
  44. data/lib/async/enumerable/methods/slicers.rb +34 -0
  45. data/lib/async/enumerable/methods/transformers/compact.rb +18 -0
  46. data/lib/async/enumerable/methods/transformers/filter_map.rb +19 -0
  47. data/lib/async/enumerable/methods/transformers/flat_map.rb +20 -0
  48. data/lib/async/enumerable/methods/transformers/map.rb +22 -0
  49. data/lib/async/enumerable/methods/transformers/reject.rb +19 -0
  50. data/lib/async/enumerable/methods/transformers/select.rb +21 -0
  51. data/lib/async/enumerable/methods/transformers/sort.rb +18 -0
  52. data/lib/async/enumerable/methods/transformers/sort_by.rb +19 -0
  53. data/lib/async/enumerable/methods/transformers/uniq.rb +18 -0
  54. data/lib/async/enumerable/methods/transformers.rb +35 -0
  55. data/lib/async/enumerable/methods.rb +26 -0
  56. data/lib/async/enumerable/version.rb +10 -0
  57. data/lib/async/enumerable.rb +72 -0
  58. data/lib/async/enumerator.rb +33 -0
  59. data/lib/enumerable/async.rb +38 -0
  60. data/scripts/debug_config.rb +26 -0
  61. data/scripts/debug_config2.rb +34 -0
  62. data/scripts/sketch.rb +30 -0
  63. data/scripts/test_aggregators.rb +66 -0
  64. data/scripts/test_ancestors.rb +12 -0
  65. data/scripts/test_async_chaining.rb +30 -0
  66. data/scripts/test_direct_method_calls.rb +53 -0
  67. data/scripts/test_example.rb +37 -0
  68. data/scripts/test_issue_7.rb +69 -0
  69. data/scripts/test_method_source.rb +15 -0
  70. metadata +145 -0
data/Rakefile ADDED
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
11
+
12
+ desc "Run quick benchmark overview"
13
+ task :benchmark_quick do
14
+ require "benchmark"
15
+ require_relative "lib/async/enumerable"
16
+
17
+ # Simulate IO operations with random delays
18
+ def io_operation(n)
19
+ sleep(rand / 1000.0) # Sleep 0-1ms to simulate IO
20
+ n * 2
21
+ end
22
+
23
+ def expensive_check(n)
24
+ sleep(rand / 1000.0) # Sleep 0-1ms to simulate IO
25
+ n % 10 == 0
26
+ end
27
+
28
+ puts "Async::Enumerable Benchmark Comparison"
29
+ puts "=" * 50
30
+ puts "Simulating IO operations with 0-1ms delays"
31
+ puts
32
+
33
+ # Test different array sizes
34
+ [10, 100, 1000, 10000].each do |size|
35
+ array = (1..size).to_a
36
+
37
+ puts "\nArray size: #{size} elements"
38
+ puts "-" * 30
39
+
40
+ Benchmark.bm(20) do |x|
41
+ # Map benchmark
42
+ x.report("sync map:") do
43
+ array.map { |n| io_operation(n) }
44
+ end
45
+
46
+ x.report("async map:") do
47
+ array.async.map { |n| io_operation(n) }
48
+ end
49
+
50
+ # For very large collections, also test with custom max_fibers
51
+ if size >= 1000
52
+ x.report("async map (100f):") do
53
+ array.async(max_fibers: 100).map { |n| io_operation(n) }
54
+ end
55
+ end
56
+
57
+ # Select benchmark
58
+ x.report("sync select:") do
59
+ array.select { |n| expensive_check(n) }
60
+ end
61
+
62
+ x.report("async select:") do
63
+ array.async.select { |n| expensive_check(n) }
64
+ end
65
+
66
+ # Any? benchmark (with early termination)
67
+ x.report("sync any?:") do
68
+ array.any? { |n| expensive_check(n) }
69
+ end
70
+
71
+ x.report("async any?:") do
72
+ array.async.any? { |n| expensive_check(n) }
73
+ end
74
+
75
+ # Find benchmark (with early termination)
76
+ target = size / 2
77
+ x.report("sync find:") do
78
+ array.find { |n| n == target }
79
+ end
80
+
81
+ x.report("async find:") do
82
+ array.async.find { |n|
83
+ sleep(rand / 1000.0)
84
+ n == target
85
+ }
86
+ end
87
+ end
88
+ end
89
+
90
+ puts "\n" + "=" * 50
91
+ puts "Note: Async methods show performance benefits when:"
92
+ puts " - Operations involve IO (network, disk, etc.)"
93
+ puts " - Collection size is large enough to offset async overhead"
94
+ end
95
+
96
+ desc "Run detailed benchmarks with clear comparisons"
97
+ task :benchmark do
98
+ puts "=" * 80
99
+ puts "Async::Enumerable Benchmarks"
100
+ puts "=" * 80
101
+ puts
102
+
103
+ # Size comparison benchmarks
104
+ puts "📊 varying collection sizes"
105
+ puts "-" * 40
106
+ puts "\nThese benchmarks compare sync vs async performance across different collection sizes."
107
+ puts "IO operations are simulated with sleep delays.\n\n"
108
+
109
+ Dir.glob("benchmark/size_comparison/*.yaml").sort.each do |file|
110
+ size = File.basename(file, ".yaml").split("_").last
111
+ puts "\nCollection Size: #{size} items"
112
+ system("bundle exec benchmark-driver #{file} 2>/dev/null")
113
+ end
114
+
115
+ # Early termination benchmarks
116
+ puts "\n\n" + "=" * 80
117
+ puts "⚡ early termination benchmarks"
118
+ puts "-" * 40
119
+ puts "\nThese benchmarks test methods that can terminate early (any?, find, etc.)."
120
+ puts "They demonstrate async performance benefits even with early termination.\n\n"
121
+
122
+ Dir.glob("benchmark/early_termination/*.yaml").sort.each do |file|
123
+ name = File.basename(file, ".yaml").tr("_", " ")
124
+ puts "\n#{name}"
125
+ system("bundle exec benchmark-driver #{file} 2>/dev/null")
126
+ end
127
+ end
@@ -0,0 +1,38 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operation for validation
5
+ def valid_item?(n, max_value = 1000)
6
+ sleep(rand / 1000.0) # Sleep 0-1ms to simulate IO
7
+ n < max_value
8
+ end
9
+
10
+ all_valid = (1..50).to_a
11
+ early_invalid = [1, 2, 1001, 4, 5] + (6..50).to_a # Invalid at position 3
12
+ mid_invalid = (1..25).to_a + [1001] + (27..50).to_a # Invalid at middle
13
+ late_invalid = (1..49).to_a + [1001] # Invalid at end
14
+
15
+ benchmark:
16
+ sync_all_valid: |
17
+ all_valid.all? { |n| valid_item?(n) }
18
+
19
+ async_all_valid: |
20
+ all_valid.async.all? { |n| valid_item?(n) }
21
+
22
+ sync_early_fail: |
23
+ early_invalid.all? { |n| valid_item?(n) }
24
+
25
+ async_early_fail: |
26
+ early_invalid.async.all? { |n| valid_item?(n) }
27
+
28
+ sync_mid_fail: |
29
+ mid_invalid.all? { |n| valid_item?(n) }
30
+
31
+ async_mid_fail: |
32
+ mid_invalid.async.all? { |n| valid_item?(n) }
33
+
34
+ sync_late_fail: |
35
+ late_invalid.all? { |n| valid_item?(n) }
36
+
37
+ async_late_fail: |
38
+ late_invalid.async.all? { |n| valid_item?(n) }
@@ -0,0 +1,39 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operation with early termination potential
5
+ def expensive_check(n, target)
6
+ sleep(rand / 1000.0) # Sleep 0-1ms to simulate IO
7
+ n == target
8
+ end
9
+
10
+ # Arrays with target at different positions
11
+ early_match = (1..100).to_a # Target at position 10
12
+ mid_match = (1..100).to_a # Target at position 50
13
+ late_match = (1..100).to_a # Target at position 90
14
+ no_match = (1..100).to_a # No target
15
+
16
+ benchmark:
17
+ sync_early: |
18
+ early_match.any? { |n| expensive_check(n, 10) }
19
+
20
+ async_early: |
21
+ early_match.async.any? { |n| expensive_check(n, 10) }
22
+
23
+ sync_mid: |
24
+ mid_match.any? { |n| expensive_check(n, 50) }
25
+
26
+ async_mid: |
27
+ mid_match.async.any? { |n| expensive_check(n, 50) }
28
+
29
+ sync_late: |
30
+ late_match.any? { |n| expensive_check(n, 90) }
31
+
32
+ async_late: |
33
+ late_match.async.any? { |n| expensive_check(n, 90) }
34
+
35
+ sync_no_match: |
36
+ no_match.any? { |n| expensive_check(n, 200) }
37
+
38
+ async_no_match: |
39
+ no_match.async.any? { |n| expensive_check(n, 200) }
@@ -0,0 +1,51 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO side effect operation
5
+ # Scale down delay for larger collections to avoid timeouts
6
+ def process_item(n, size = 100)
7
+ delay = case size
8
+ when 1..100 then rand / 1000.0 # 0-1ms
9
+ when 101..1000 then rand / 5000.0 # 0-0.2ms
10
+ else rand / 50000.0 # 0-0.02ms for 10000
11
+ end
12
+ sleep(delay)
13
+ # Side effect: would normally write to file, database, etc.
14
+ n * 2
15
+ end
16
+
17
+ array_10 = (1..10).to_a
18
+ array_100 = (1..100).to_a
19
+ array_1000 = (1..1000).to_a
20
+ array_10000 = (1..10000).to_a
21
+
22
+ benchmark:
23
+ sync_10: |
24
+ array_10.each { |n| process_item(n, 10) }
25
+
26
+ async_10: |
27
+ array_10.async.each { |n| process_item(n, 10) }
28
+
29
+ sync_100: |
30
+ array_100.each { |n| process_item(n, 100) }
31
+
32
+ async_100: |
33
+ array_100.async.each { |n| process_item(n, 100) }
34
+
35
+ sync_1000: |
36
+ array_1000.each { |n| process_item(n, 1000) }
37
+
38
+ async_1000: |
39
+ array_1000.async.each { |n| process_item(n, 1000) }
40
+
41
+ async_1000_limited: |
42
+ array_1000.async(max_fibers: 100).each { |n| process_item(n, 1000) }
43
+
44
+ sync_10000: |
45
+ array_10000.each { |n| process_item(n, 10000) }
46
+
47
+ async_10000: |
48
+ array_10000.async.each { |n| process_item(n, 10000) }
49
+
50
+ async_10000_limited: |
51
+ array_10000.async(max_fibers: 100).each { |n| process_item(n, 10000) }
@@ -0,0 +1,37 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operation for finding an item
5
+ def matches_criteria(n, target)
6
+ sleep(rand / 1000.0) # Sleep 0-1ms to simulate IO
7
+ n == target
8
+ end
9
+
10
+ small_array = (1..20).to_a
11
+ medium_array = (1..100).to_a
12
+ large_array = (1..500).to_a
13
+
14
+ benchmark:
15
+ sync_small_early: |
16
+ small_array.find { |n| matches_criteria(n, 5) }
17
+
18
+ async_small_early: |
19
+ small_array.async.find { |n| matches_criteria(n, 5) }
20
+
21
+ sync_medium_mid: |
22
+ medium_array.find { |n| matches_criteria(n, 50) }
23
+
24
+ async_medium_mid: |
25
+ medium_array.async.find { |n| matches_criteria(n, 50) }
26
+
27
+ sync_large_late: |
28
+ large_array.find { |n| matches_criteria(n, 450) }
29
+
30
+ async_large_late: |
31
+ large_array.async.find { |n| matches_criteria(n, 450) }
32
+
33
+ sync_not_found: |
34
+ medium_array.find { |n| matches_criteria(n, 1000) }
35
+
36
+ async_not_found: |
37
+ medium_array.async.find { |n| matches_criteria(n, 1000) }
@@ -0,0 +1,50 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operations with varying delays
5
+ # Scale down delay for larger collections to avoid timeouts
6
+ def io_operation(n, size = 100)
7
+ delay = case size
8
+ when 1..100 then rand / 1000.0 # 0-1ms
9
+ when 101..1000 then rand / 5000.0 # 0-0.2ms
10
+ else rand / 50000.0 # 0-0.02ms for 10000
11
+ end
12
+ sleep(delay)
13
+ n * 2
14
+ end
15
+
16
+ array_10 = (1..10).to_a
17
+ array_100 = (1..100).to_a
18
+ array_1000 = (1..1000).to_a
19
+ array_10000 = (1..10000).to_a
20
+
21
+ benchmark:
22
+ sync_10: |
23
+ array_10.map { |n| io_operation(n, 10) }
24
+
25
+ async_10: |
26
+ array_10.async.map { |n| io_operation(n, 10) }
27
+
28
+ sync_100: |
29
+ array_100.map { |n| io_operation(n, 100) }
30
+
31
+ async_100: |
32
+ array_100.async.map { |n| io_operation(n, 100) }
33
+
34
+ sync_1000: |
35
+ array_1000.map { |n| io_operation(n, 1000) }
36
+
37
+ async_1000: |
38
+ array_1000.async.map { |n| io_operation(n, 1000) }
39
+
40
+ async_1000_limited: |
41
+ array_1000.async(max_fibers: 100).map { |n| io_operation(n, 1000) }
42
+
43
+ sync_10000: |
44
+ array_10000.map { |n| io_operation(n, 10000) }
45
+
46
+ async_10000: |
47
+ array_10000.async.map { |n| io_operation(n, 10000) }
48
+
49
+ async_10000_limited: |
50
+ array_10000.async(max_fibers: 100).map { |n| io_operation(n, 10000) }
@@ -0,0 +1,31 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operation that checks a condition
5
+ def check_condition(n)
6
+ sleep(rand / 1000.0) # Sleep 0-1ms to simulate IO
7
+ n % 3 == 0
8
+ end
9
+
10
+ small_array = (1..20).to_a
11
+ medium_array = (1..100).to_a
12
+ large_array = (1..500).to_a
13
+
14
+ benchmark:
15
+ sync_small: |
16
+ small_array.select { |n| check_condition(n) }
17
+
18
+ async_small: |
19
+ small_array.async.select { |n| check_condition(n) }
20
+
21
+ sync_medium: |
22
+ medium_array.select { |n| check_condition(n) }
23
+
24
+ async_medium: |
25
+ medium_array.async.select { |n| check_condition(n) }
26
+
27
+ sync_large: |
28
+ large_array.select { |n| check_condition(n) }
29
+
30
+ async_large: |
31
+ large_array.async.select { |n| check_condition(n) }
@@ -0,0 +1,17 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operation for checking
5
+ def expensive_check(n)
6
+ sleep(rand / 1000.0) # 0-1ms delay
7
+ n > 5 # Will match early in the array
8
+ end
9
+
10
+ array = (1..100).to_a
11
+
12
+ benchmark:
13
+ "Sync any? (early match)": |
14
+ array.any? { |n| expensive_check(n) }
15
+
16
+ "Async any? (early match)": |
17
+ array.async.any? { |n| expensive_check(n) }
@@ -0,0 +1,17 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operation for checking
5
+ def expensive_check(n)
6
+ sleep(rand / 1000.0) # 0-1ms delay
7
+ n > 95 # Will match late in the array
8
+ end
9
+
10
+ array = (1..100).to_a
11
+
12
+ benchmark:
13
+ "Sync any? (late match)": |
14
+ array.any? { |n| expensive_check(n) }
15
+
16
+ "Async any? (late match)": |
17
+ array.async.any? { |n| expensive_check(n) }
@@ -0,0 +1,17 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operation for searching
5
+ def matches_criteria?(n)
6
+ sleep(rand / 1000.0) # 0-1ms delay
7
+ n == 50 # Will find in the middle
8
+ end
9
+
10
+ array = (1..100).to_a
11
+
12
+ benchmark:
13
+ "Sync find (middle)": |
14
+ array.find { |n| matches_criteria?(n) }
15
+
16
+ "Async find (middle)": |
17
+ array.async.find { |n| matches_criteria?(n) }
@@ -0,0 +1,17 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operations with 0-1ms delay
5
+ def io_operation(n)
6
+ sleep(rand / 1000.0)
7
+ n * 2
8
+ end
9
+
10
+ array = (1..10).to_a
11
+
12
+ benchmark:
13
+ "Sync (10 items)": |
14
+ array.map { |n| io_operation(n) }
15
+
16
+ "Async (10 items)": |
17
+ array.async.map { |n| io_operation(n) }
@@ -0,0 +1,17 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operations with 0-1ms delay
5
+ def io_operation(n)
6
+ sleep(rand / 1000.0)
7
+ n * 2
8
+ end
9
+
10
+ array = (1..100).to_a
11
+
12
+ benchmark:
13
+ "Sync (100 items)": |
14
+ array.map { |n| io_operation(n) }
15
+
16
+ "Async (100 items)": |
17
+ array.async.map { |n| io_operation(n) }
@@ -0,0 +1,20 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operations with reduced delay for large collections
5
+ def io_operation(n)
6
+ sleep(rand / 5000.0) # 0-0.2ms
7
+ n * 2
8
+ end
9
+
10
+ array = (1..1000).to_a
11
+
12
+ benchmark:
13
+ "Sync (1000 items)": |
14
+ array.map { |n| io_operation(n) }
15
+
16
+ "Async (1000 items)": |
17
+ array.async.map { |n| io_operation(n) }
18
+
19
+ "Async(100f) (1000 items)": |
20
+ array.async(max_fibers: 100).map { |n| io_operation(n) }
@@ -0,0 +1,23 @@
1
+ prelude: |
2
+ require 'async/enumerable'
3
+
4
+ # Simulate IO operations with minimal delay for very large collections
5
+ def io_operation(n)
6
+ sleep(rand / 50000.0) # 0-0.02ms
7
+ n * 2
8
+ end
9
+
10
+ array = (1..10000).to_a
11
+
12
+ benchmark:
13
+ "Sync (10000 items)": |
14
+ array.map { |n| io_operation(n) }
15
+
16
+ "Async (10000 items)": |
17
+ array.async.map { |n| io_operation(n) }
18
+
19
+ "Async(100f) (10000 items)": |
20
+ array.async(max_fibers: 100).map { |n| io_operation(n) }
21
+
22
+ "Async(500f) (10000 items)": |
23
+ array.async(max_fibers: 500).map { |n| io_operation(n) }
@@ -0,0 +1,43 @@
1
+ # Async::Enumerable Reference Documentation
2
+
3
+ This directory contains detailed reference documentation for the async-enumerable gem.
4
+
5
+ ## Core Components
6
+
7
+ - [Async::Enumerable Module](enumerable.md) - Main module for adding async capabilities to enumerables
8
+ - [Async::Enumerator Class](enumerator.md) - Wrapper class providing async enumerable methods
9
+ - [ConcurrencyBounder Module](concurrency_bounder.md) - Bounded concurrency control
10
+
11
+ ## Method Categories
12
+
13
+ ### [Predicate Methods](methods/predicates.md)
14
+ - `all?`, `any?`, `none?`, `one?`
15
+ - `find`, `find_index`
16
+ - `include?`, `member?`
17
+
18
+ ### [Transformer Methods](methods/transformers.md)
19
+ - `map`, `select`, `reject`
20
+ - `filter_map`, `flat_map`
21
+ - `compact`, `uniq`, `sort`, `sort_by`
22
+
23
+ ### [Converter Methods](methods/converters.md)
24
+ - `to_a` - Convert to array
25
+ - `sync` - Materialize async chain results
26
+
27
+ ## Quick Start
28
+
29
+ For basic usage, see the main [README](../../README.md). For detailed API documentation, explore the files linked above.
30
+
31
+ ## Note on Documentation Style
32
+
33
+ The source code contains terse YARD documentation (2-3 lines) for quick reference. These detailed markdown files provide comprehensive documentation including:
34
+
35
+ - Detailed method descriptions
36
+ - Parameter explanations
37
+ - Return value documentation
38
+ - Usage examples
39
+ - Implementation notes
40
+ - Performance considerations
41
+ - Common patterns
42
+
43
+ This separation keeps the source code clean and readable while maintaining thorough documentation.