Feature #21615
closedIntroduce `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
ArrayandHashalready implement#values_at. -
Hashimplements#valuesbutArraydoes 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#eachalready covers iteration.
Updated by nobu (Nobuyoshi Nakada) 2 months 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) 2 months ago
ยท Edited
matheusrich (Matheus Richard) wrote:
Following the Principle of Least Surprise, users may reasonably expect
Array#valuesto 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) 2 months 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) 2 months ago
- Description updated (diff)
Updated by Dan0042 (Daniel DeLorme) 2 months 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) 2 months 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) about 1 month 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#valuestoo.
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 1 month 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#keysbe defined as a method that returns(0...ary.size).to_a? -
Array#find_indexandHash#keyare (roughly) counterparts. Should they be aliased to each other? - Should
Hash#each_keyandArray#each_indexalso 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#sortwould be incompatible withEnumerable#sort). - Should methods like
Array#rehashandArray#compare_by_identitybe provided (perhaps as no-ops)? - Wouldn't
Array#defaultandArray#default=also be necessary? - Should operators like
Array#<=be defined to align withHash#<=? - While
Array#transform_valuescould be defined straightforwardly, how shouldArray#transform_keysbehave? - The different meanings of
Array#include?andHash#include?are surprising. - The different meanings of
Array#assocandHash#assocare 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.
Updated by matz (Yukihiro Matsumoto) about 1 month ago
- Status changed from Open to Rejected
HI,
- Don't use principle of least surprise as your rationale, since background and assumption vary person to person
- I don't think Hashes can be naturally considered as Arrays with non-integer indexes. You can tell it from Hash#each behavior, for example
- Don't mention principle of least surprise in the discussion here
Matz.