Project

General

Profile

Actions

Feature #21615

open

Introduce `Array#values`

Feature #21615: Introduce `Array#values`

Added by matheusrich (Matheus Richard) 23 days ago. Updated about 17 hours ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:123319]

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 and Hash already implement #values_at.
  • Hash implements #values but Array 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 to to_a, but I found it too broad of a change.
  • Array#each_value: redundant as Array#each already covers iteration.

Patch: https://github.com/ruby/ruby/pull/14641

Updated by nobu (Nobuyoshi Nakada) 23 days ago Actions #1 [ruby-core:123320]

matheusrich (Matheus Richard) wrote:

Add Array#values, returning self. Implementation could simply alias to itself:

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 Actions #2 [ruby-core:123321]

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 Actions #3 [ruby-core:123322]

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 Actions #4

  • Description updated (diff)

Updated by Dan0042 (Daniel DeLorme) 22 days ago Actions #5 [ruby-core:123323]

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 Actions #6 [ruby-core:123324]

@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 Actions #7 [ruby-core:123481]

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.

  1. https://clojure.org/reference/sequences

Updated by mame (Yusuke Endoh) about 17 hours ago 1Actions #8 [ruby-core:123488]

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 and Hash#key are (roughly) counterparts. Should they be aliased to each other?
  • Should Hash#each_key and Array#each_index also be aliased to each other?
  • What about order-related operations? Wouldn't we also need methods like Hash#reverse, Hash#rotate, and Hash#sort? (Note that Hash#sort would be incompatible with Enumerable#sort).
  • Should methods like Array#rehash and Array#compare_by_identity be provided (perhaps as no-ops)?
  • Wouldn't Array#default and Array#default= also be necessary?
  • Should operators like Array#<= be defined to align with Hash#<=?
  • While Array#transform_values could be defined straightforwardly, how should Array#transform_keys behave?
  • The different meanings of Array#include? and Hash#include? are surprising.
  • The different meanings of Array#assoc and Hash#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.

Actions

Also available in: PDF Atom