Feature #14399
openAdd Enumerable#product
Added by jzakiya (Jabari Zakiya) almost 7 years ago. Updated over 5 years ago.
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
.
Updated by shan (Shannon Skipper) almost 7 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 7 years 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.
Updated by jzakiya (Jabari Zakiya) almost 7 years ago
Duh, ok, its an Array
method.
Updated by jzakiya (Jabari Zakiya) almost 7 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 7 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 7 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 7 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 7 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 7 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 5 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