
Issue #20953 has been updated by zverok (Victor Shepelev).
`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? ---------------------------------------- Feature #20953: Array#fetch_values vs #values_at protocols https://bugs.ruby-lang.org/issues/20953#change-111024 * Author: zverok (Victor Shepelev) * Status: Open ---------------------------------------- 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: ```ruby [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: ```ruby 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.) -- https://bugs.ruby-lang.org/