Project

General

Profile

Feature #14399

Add Enumerable#product

Added by jzakiya (Jabari Zakiya) 7 months ago. Updated 7 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:85085]

Description

For similar reasons for creating Enumerable#sum a companion method
Enumerable#product is also very useful. Taking the product of
numbers in arrays is a common operation in many numerical algorithms,
especially in number theory and cryptography, and its optimization in
Ruby will make it more conducive to math heavy algorithms and tasks.

This

> [2,3,5,7].reduce(:*) => 210

can be optimized to this

> [2,3,5,7].product => 210

It should also allow an initial value

> [2,3,5,7].product(2) => 420

> [2,3,5,7].product(0.5) => 105

Crystal already has this method.

History

#1 Updated by shan (Shannon Skipper) 7 months ago

Array#product would stomp on Enumerable#product, and it confusingly does a different thing:

[2,3,5,7].product => [[2], [3], [5], [7]]

Maybe Enumerable#multiply would compliment Enumerable#sum?

#2 [ruby-core:85089] Updated by jzakiya (Jabari Zakiya) 7 months ago

I looked here https://ruby-doc.org/core-2.5.0/Enumerable.html and didn't see
that method, so I didn't know it existed. But multiply would also work.

Where in the docs is product?

The Ruby product then causes a conflict with the Crystal definition/use.

#3 [ruby-core:85090] Updated by jzakiya (Jabari Zakiya) 7 months ago

Duh, ok, its an Array method.

#4 [ruby-core:85091] Updated by jzakiya (Jabari Zakiya) 7 months ago

Actually, after looking at the docs for Array#product a
better name would be Array#combinations, which is a little
longer, but I think is more accurately descriptive and intuitive.
Oh well, cats out the bag now.

#5 [ruby-core:85109] Updated by shevegen (Robert A. Heiler) 7 months ago

which is a little longer, but I think is more accurately
descriptive and intuitive.

We are back to the ancient problem - giving things good names. :-)

The only name from the above that I like is .sum - the others
do not tell me much at all. ;-)

I am not sure what .combinations would do - then again I also
never fully understood the name .reduce() either. People
often associate different actions with the same (but also
different) names.

#6 [ruby-core:85123] Updated by jzakiya (Jabari Zakiya) 7 months ago

That's interesting because I never intuitively understood the use
of inject, especially for numerical purposes, and always now use
reduce, which was (I believe) created by Haskell (the person and
programming language), and has long been a concept in functional
programming. To me it says that an array of numbers (items) will be
reduced|collapsed into a single value based on the given operation.

Also, after reading your comment and thinking about it, there is a way
to reclaim the product method name and recast it for this new (better) use.

A better name for the current Array#product is Array#combine, which has
the same number of characters, so it can replace the word inplace in code
without the need to possibly reformat it.

[1,2,3].product [5,6,7] which seems like a vector|matrix operation

can be renamed as

[1,2,3].combine [5,6,7] or even [1,2,3].combine_with [5,6,7}

I think this is much clearer indication of its intent and purpose.

Here's a proposed path to reclaim and recast product as proposed.

Ruby 2.6
Introduce Array#combine(_with) as an alias to the current Array#product.
Replace its use in the Ruby core and stdlib code.
Make announcement to deprecate use of Array#product to give people time to change.

Introduce Enumerable#multiply to act as proposed Enumerable#product.

Ruby 2.7
Eliminate use of Array#product
Create Enumerable#product as alias of Enumerable#multiply

Since everyone knows Ruby 3 is going to be a breaking some old code change, if
people are weaned off old code use then this change really won't create problems.
On one hand you are creating a new capability in Enumerable#[product|multiply]
while keeping, but renaming, a current capability. Plus, I can't think that alot of
code|people currently use Array#product, so I doubt it affects a lot of code|people.
(Is this ever used in Rails, etc?)

Python went through this going from 2 to 3, famously with the print method change.

Basically what I'm illustrating, if there is a will there is a way to make this change,
without a lot of disruption and pain.

#7 [ruby-core:85165] Updated by nate00 (Nate Sullivan) 7 months ago

I believe Enumerable#product is named after the Cartesian product (also called just "product" in set theory). For example, see the discussion in issue 7444. Renaming it to combinations or combine might cause people to think it's related to mathematical combinations, but as far as I can tell, it isn't related.

#8 [ruby-core:85195] Updated by jzakiya (Jabari Zakiya) 7 months ago

According to the thread you provided the initial suggested name was Array#product_set
which was shortened to the current Array#product. Its proposed intent was to provide
combination groups of set-like items, and not as an arithmetic operation.

As was stated previously, naming things is Hard!, especially when you can't anticipate all
the unintended consequences.

I think Crystal has gotten it correct for this, having both Enumerable#product and
Enumerable#sum methods. I suspect the overwhelming majority of programmers|users
coming to Ruby would expects the following behavior too.

[2,3,5,7].sum => 17        # the result of adding all the array elements

[2,3,5,7].product => 210   # the result of multiplying all the array elements

#9 [ruby-core:85317] Updated by joanbm (Joan Blackmoore) 7 months ago

EDIT: The original request was updated and my comment is related to the meaning of previous one. I don't agree with adding method of identical name but distinct semantic, ie. existing Array#product as a Cartesian product of elements and Enumerable#product as a product of their multiplication. It would only bring inconsistency and confusion, esp. conflicting while Array includes Enumerable.

I'd also vote YES for this feature request, move #product into Enumerable.

It is especially missing when working with (lazy) infinite sequences and problems leading to cartesian product of elements. Now it has to be implemented by nesting blocks of Enumerable#flat_map, which is cumbersome and inelegant when, ironically, there is method already available but for Array (sub)class(es) only.

p.s. Please stay on topic, discussion about chosen method name is pointless because it is already there. Backward compatibility should be kept as possible and its name is also justified although opinions may differ.

Also available in: Atom PDF