[ruby-core:117056] [Ruby master Bug#20325] Enumerator.product.size bug with zero * infinite enumerators

Issue #20325 has been reported by marcandre (Marc-Andre Lafortune). ---------------------------------------- Bug #20325: Enumerator.product.size bug with zero * infinite enumerators https://bugs.ruby-lang.org/issues/20325 * Author: marcandre (Marc-Andre Lafortune) * Status: Open * Target version: 3.4 * ruby -v: ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- ``` ruby Enumerator.product([], 1..).to_a # => [] (OK) Enumerator.product([], 1..).size # => Infinity (Should be 0) ``` -- https://bugs.ruby-lang.org/

Issue #20325 has been updated by eileencodes (Eileen Uchitelle). I don't think this is a bug, the documentation and tests say the current behavior is intentional:
"Returns the total size of the enumerator product calculated by multiplying the sizes of enumerables in the product. If any of the enumerables reports its size as nil or Float::INFINITY, that value is returned as the size."
https://docs.ruby-lang.org/en/master/Enumerator/Product.html#method-i-size ---------------------------------------- Bug #20325: Enumerator.product.size bug with zero * infinite enumerators https://bugs.ruby-lang.org/issues/20325#change-107131 * Author: marcandre (Marc-Andre Lafortune) * Status: Open * Target version: 3.4 * ruby -v: ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- ``` ruby Enumerator.product([], 1..).to_a # => [] (OK) Enumerator.product([], 1..).size # => Infinity (Should be 0) ``` -- https://bugs.ruby-lang.org/

Issue #20325 has been updated by shan (Shannon Skipper). I agree the behavior aligns with the documentation and tests. On the other hand, there seem to be two scenarios where the lazily calculated size of infinity doesn't line up with the determinable product count. One, referenced above, is when there's both an infinite size and a `0` so we know the product will be `0` but infinity is returned. Another is when an infinite size precedes a `nil` so the product isn't actually lazily determinable but it returns infinity rather than `nil`. ``` ruby ## # Product count will be zero. enums_zero = [[], 1..] ## # Product count is unknown. enums_nil = [1.., Enumerator.new {}] ``` Considering the current `enum_product_total_size(VALUE enums)` roughly translated to Ruby: ``` ruby def enum_product_total_size(enums) enums.reduce(1) do |total, enum| size = enum.size return size if size.nil? || size.infinite? return nil unless size.is_a?(Integer) total * size end end enum_product_total_size(enums_zero) #=> Float::INFINITY p enum_product_total_size(enums_nil) #=> Float::INFINITY ``` It feels like we should short circuit and return `nil` if any size is `nil` and return `0` rather than `Float::INFINITY` when both are present. One option would be to first check for any `nil` then add a check for any `0` before proceeding with infinity and integer checks and multiplication. ``` ruby def enum_product_total_size(enums) sizes = enums.map(&:size) return nil if sizes.include?(nil) return 0 if sizes.include?(0) return Float::INFINITY if sizes.include?(Float::INFINITY) return nil unless sizes.all?(Integer) sizes.reduce(1, :*) end enum_product_total_size(enums_zero) #=> 0 p enum_product_total_size(enums_nil) #=> nil ``` It seems to me the documented lazy sizes are wrong in these two cases. Would fixing these lazily calculated sizes to align with the actual product count be worth doing? It seems like a nice things to do to me, unless there are concerns that it's a breaking change since folk are relying on a dubious lazy `Float::INFINITY` size that doesn't match the count. ---------------------------------------- Bug #20325: Enumerator.product.size bug with zero * infinite enumerators https://bugs.ruby-lang.org/issues/20325#change-107152 * Author: marcandre (Marc-Andre Lafortune) * Status: Open * Target version: 3.4 * ruby -v: ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- ``` ruby Enumerator.product([], 1..).to_a # => [] (OK) Enumerator.product([], 1..).size # => Infinity (Should be 0) ``` -- https://bugs.ruby-lang.org/

Issue #20325 has been updated by shan (Shannon Skipper). It seems like an infinite size with a `0` always has a product count of `0`. An infinite size preceding a `nil` has either a product count of `0` or infinity, but it's not lazily calculable so seems like it should be `nil`. ---------------------------------------- Bug #20325: Enumerator.product.size bug with zero * infinite enumerators https://bugs.ruby-lang.org/issues/20325#change-107153 * Author: marcandre (Marc-Andre Lafortune) * Status: Open * Target version: 3.4 * ruby -v: ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22] * Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- ``` ruby Enumerator.product([], 1..).to_a # => [] (OK) Enumerator.product([], 1..).size # => Infinity (Should be 0) ``` -- https://bugs.ruby-lang.org/
participants (3)
-
eileencodes (Eileen Uchitelle)
-
marcandre (Marc-Andre Lafortune)
-
shan (Shannon Skipper)