Feature #21615
openIntroduce `Array#values`
Description
Motivation¶
In Ruby code, it's common to accept arrays and hashes and treat them uniformly as collections of values. Hash
exposes #values
, but Array
does not, which pushes developers toward is_a?
/respond_to?
branching.
Following the Principle of Least Surprise, users may reasonably expect Array#values
to exist because:
- Both
Array
andHash
already implement#values_at
. -
Hash
implements#values
butArray
does not.
Example¶
Today:
def normalize_records(input)
items = input.respond_to?(:values) ? input.values : input
items.each do |item|
do_stuff_with_item(item)
end
end
With Array#values
:
def normalize_records(input)
input.values.each do |item|
do_stuff_with_item(item)
end
end
Proposal¶
Add Array#values
, returning a copy of the elements of self
.
This yields a uniform interface for Array
and Hash
values without type checks.
Alternatives considered¶
-
Enumerable#values
: defaulting toto_a
, but I found it too broad of a change. -
Array#each_value
: redundant asArray#each
already covers iteration.
Updated by nobu (Nobuyoshi Nakada) 25 days ago
matheusrich (Matheus Richard) wrote:
Add
Array#values
, returningself
. Implementation could simply alias toitself
:
I think it should be Array.new(*self)
.
Hash#values
returns a new Array
, non-frozen and independent from the receiver.
Updated by nobu (Nobuyoshi Nakada) 25 days ago
ยท Edited
matheusrich (Matheus Richard) wrote:
Following the Principle of Least Surprise, users may reasonably expect
Array#values
to exist because:
"PoLS" is a word not to say at a proposal.
That word is negative to convince us.
We are not sure who started to say the word, but Ruby itself hasn't advertised it.
With
Array#values
:def normalize_records(input) input.values.each do |item| do_stuff_with_item(item) end end
Why not to introduce Array#each_value
?
Updated by matheusrich (Matheus Richard) 25 days ago
I think it should be Array.new(*self).
I can do that, sure.
That word is negative to convince us.
Noted! Good to know.
Why not to introduce Array#each_value?
I think it feels clunkier than array.values.each
, but is an alternative I considered. IMO it still makes me expect that a #values
method would exist since it would be a word present in values_at
, fetch_values
and each_value
.
IMO both could be added, so Array has an interface even more similar to Hash, but I decided to keep this small.
Updated by matheusrich (Matheus Richard) 25 days ago
- Description updated (diff)
Updated by Dan0042 (Daniel DeLorme) 25 days ago
I understand the idea of making core collection classes ducktype-compatible, but in that case this proposal should also include Set#values
Updated by matheusrich (Matheus Richard) 25 days ago
@Dan0042 I considered that. I thought it would be too much for one PR. I'll wait for more feedback, and if people are positive about this, I'll propose Set#values
too.
Updated by brightraven (Billy G. Jaime Beltran) 4 days ago
matheusrich (Matheus Richard) wrote in #note-6:
@Dan0042 I considered that. I thought it would be too much for one PR. I'll wait for more feedback, and if people are positive about this, I'll propose
Set#values
too.
One principle I have enjoyed heavily from other programming languages that use this type of duck typing is the idea of a higher abstraction called a sequence[1] over which anything can be iterated, be it a set, a hash or an array. It would be pleasant to have this for all collections equally.
Updated by mame (Yusuke Endoh) 3 days ago
This feels like opening Pandora's box. If this is accepted, I can foresee the following discussions arising, just off the top of my head:
- Should
Array#keys
be defined as a method that returns(0...ary.size).to_a
? -
Array#find_index
andHash#key
are (roughly) counterparts. Should they be aliased to each other? - Should
Hash#each_key
andArray#each_index
also be aliased to each other? - What about order-related operations? Wouldn't we also need methods like
Hash#reverse
,Hash#rotate
, andHash#sort
? (Note thatHash#sort
would be incompatible withEnumerable#sort
). - Should methods like
Array#rehash
andArray#compare_by_identity
be provided (perhaps as no-ops)? - Wouldn't
Array#default
andArray#default=
also be necessary? - Should operators like
Array#<=
be defined to align withHash#<=
? - While
Array#transform_values
could be defined straightforwardly, how shouldArray#transform_keys
behave? - The different meanings of
Array#include?
andHash#include?
are surprising. - The different meanings of
Array#assoc
andHash#assoc
are surprising.
After all, I personally feel that Array
and Hash
are not inherently polymorphic.
If you want to use them polymorphically, I think you should limit their polymorphic use to #[]
only.