Project

General

Profile

Actions

Feature #20953

open

Array#fetch_values vs #values_at protocols

Added by zverok (Victor Shepelev) 7 days ago. Updated 4 days ago.

Status:
Open
Target version:
-
[ruby-core:120250]

Description

I believe that the user might expect #fetch_values to be a stricter version of #values_at, confirming to the same protocol for arguments.

But the current implementation for #fetch_values is simpler:

[1, 2, 3, 4, 5].values_at(0, 3..4)    #=> [1, 4, 5]
[1, 2, 3, 4, 5].fetch_values(0, 3..4) # TypeError: in 'Array#fetch': no implicit conversion of Range into Integer

I believe aligning the implementations would lessen confusion (even if it makes #fetch_values implementation somewhat less trivial).

The practical example of usefulness:

HEADERS = %w[Name Department]

def table_headers(rows)
  HEADERS.fetch_values(...rows.map(&:size).max) { '<unknown'> }
  # Or, alternatively:
  # HEADERS.fetch_values(...rows.map(&:size).max) { raise ArgumentError, "No header defined for column #{it + 1}" }
end

table_headers([
  ['John'],
  ['Jane'],
]) #=> ["Name"]

table_headers([
  ['John'],
  ['Jane', 'Engineering'],
]) #=> ["Name", "Department"]

table_headers([
  ['John', 'Accounting', 'Feb 24'],
  ['Jane', 'Engineering'],
]) #=> ["Name", "Department", "<unknown>"]
# or ArgumentError No header defined for column 3

(Obviously, we can use fetch_values(*(0...max_row_size)) as an argument, but it feels like an unjustified extra work when values_at already can do this.)

Updated by byroot (Jean Boussier) 6 days ago

Array#fetch_values is modeled after Hash#fetch_values, not Array#values_at.

Since Array "keys" can only possibly be integers, it makes sense for a method that is specific to Array to cast the arguments this way.

But for the method that is meant to be used indiscriminately with either Array or Hash, I don't think it's a good idea.

Updated by zverok (Victor Shepelev) 6 days ago

Array#fetch_values is modeled after Hash#fetch_values, not Array#values_at.

But in Hash, #values_at and #fetch_values have exactly the same protocol (they accept the list of keys), and can be substituted by each other.

So, I understand it is all hand-wavy, yet I’d say that the common motive of similar methods between Hash and Array is that they are, well, similar, yet internal consistency inside one class is more important than consistency between classes.

In fact, we don’t need to go far to prove that: values_at is a good example: the same name, the similar signature values_at(*objects), and yet Array’s one accepts ranges besides just indices. There are many more methods with the same name and sense (#[], #select/#reject, #include?, etc.), all of them behaving slightly differently between classes, and matching the expectation of a particular class users.

But for the method that is meant to be used indiscriminately with either Array or Hash, I don't think it's a good idea.

Do we have a lot of those, and what’s the reasoning/use cases behind this? I mean, methods that are exactly reconciled between Array and Hash (at the expense of the internal consistency inside any one class), to be used indiscriminately?

Updated by byroot (Jean Boussier) 6 days ago

Do we have a lot of those, and what’s the reasoning/use cases behind this?

Things like #dig, so that you can traverse a tree of mixed Hash/Array with some sort of "path" argument.

Either way, I see pros and cons to both behavior, so it's not for me to decide, it's up to @matz (Yukihiro Matsumoto).

The issue though is the release is imminent and the next developer meeting is scheduled for January. So if you wish to change this before the release you'll have to ping @matz (Yukihiro Matsumoto) on Twitter or something for him to make a decision in time.

Updated by zverok (Victor Shepelev) 4 days ago

  • Assignee set to matz (Yukihiro Matsumoto)
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0