Feature #14399


Add Enumerable#product

Added by jzakiya (Jabari Zakiya) almost 6 years ago. Updated over 4 years ago.

Target version:


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.


> [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.

Actions #1

Updated by shan (Shannon Skipper) almost 6 years 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?

Updated by jzakiya (Jabari Zakiya) almost 6 years ago

I looked here 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.

Updated by jzakiya (Jabari Zakiya) almost 6 years ago

Duh, ok, its an Array method.

Updated by jzakiya (Jabari Zakiya) almost 6 years 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.

Updated by shevegen (Robert A. Heiler) almost 6 years 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.

Updated by jzakiya (Jabari Zakiya) almost 6 years 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.

Updated by nate00 (Nate Sullivan) almost 6 years 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.

Updated by jzakiya (Jabari Zakiya) almost 6 years 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

Updated by joanbm (Joan Blackmoore) almost 6 years 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.

Updated by jwmittag (Jörg W Mittag) over 4 years ago

jzakiya (Jabari Zakiya) wrote:

That's interesting because I never intuitively understood the use of inject

This is bastardized from Smalltalk. In Smalltalk, method names consist of multiple words and the arguments are written in between the words. In Smalltalk, the method is called inject:into: and is called like aCollection inject: aValue into: aBlock, i.e. you inject an accumulator into a block that is executed for each element. This a) makes sense, and b) fits with the "in-joke" of naming all of Smalltalk's collection operations using names that rhyme (inject:into:, collect:, reject:, select:, detect:). Ruby is heavily based on Smalltalk, so it is generally expected that newcomers to Ruby will be familiar with Smalltalk's collection operations, therefore the slightly unusual names.

However, the meaning of "injecting an accumulator into a block" gets lost because in Ruby, we cannot write it that way, and so we lose the "into" from the name.

[I] always now use reduce, which […] has long been a concept in functional programming.

Note however, that at least in some programming languages, reduce is a restricted version of a fold which has a restricted type signature and only works on non-empty collections. In particular, in Scala, reduce is equivalent to Ruby's inject with only a block argument, and fold is equivalent to Ruby's inject with an explicit start argument. (In ECMAScript OTOH, reduce is the general version.)

reduce […] was (I believe) created by Haskell (the person and programming language)

Actually, Haskell calls the general version fold and the restricted version fold1 (because it requires at least one element, and will take element number 1 as the accumulator).

To me it says that an array of numbers (items) will be reduced|collapsed into a single value based on the given operation.

This idea of "reducing to a single value" is unfortunately somewhat misleading. A lot of people I talk to think that fold can only be used for summing, because they don't understand that a) the "single value" can be a different type from the element type and b) the "single value" can be arbitrarily complex, e.g. an array, a tree, an instruction sequence, etc.

In fact, fold is a general method of iteration, it can do everything that a loop can do. A compiler is simply a fold over the AST, for example. (The restricted version, i.e. what Haskell calls fold1 and Scala calls reduce OTOH is not general.)

Or, in short: naming is hard :-D


Also available in: Atom PDF