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) 23 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) 23 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) 23 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) 23 days ago
- Description updated (diff)
Updated by Dan0042 (Daniel DeLorme) 22 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) 22 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) 2 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) about 17 hours 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.