From: "tomstuart (Tom Stuart) via ruby-core" Date: 2023-08-04T07:42:26+00:00 Subject: [ruby-core:114344] [Ruby master Feature#19830] Allow `Array#transpose` to take an optional size argument Issue #19830 has been updated by tomstuart (Tom Stuart). nobu (Nobuyoshi Nakada) wrote in #note-1: > Why not `ary.transpose[1]&.join`? That avoids the exception but returns `nil` instead of taking advantage of `#join`���s knowledge of the ���correct��� result for an empty array, i.e. the empty string. If the caller was expecting `#transpose` to return an array of arrays, they���ll also be expecting `#join` to return a string, so the safe navigation operator just pushes the `nil` problem even further downstream. So why not `ary.transpose[1]&.join || ""` or `(ary.transpose[1] || []).join` or even `ary.transpose[1].to_a.join`? Yes, any of them will work. The optional size argument is intended to provide a more general and elegant solution so that these case-by-case `nil` workarounds can be avoided. Here is an example which is closer to a problem I encounter in real programs: ``` >> ary = [[1, :a], [2, :b], [3, :c]] >> ary.transpose.then { |numbers, letters| [numbers.sum, letters.join] } => [6, "abc"] >> ary = [] >> ary.transpose.then { |numbers, letters| [numbers.sum, letters.join] } undefined method `sum' for nil (NoMethodError) ``` How should we avoid the exception and get the result we want in this case, i.e. `[0, ""]`? I would like to avoid having to hardcode the correct ���empty array result��� value every time (e.g. `[numbers&.sum || 0, letters&.join || ""]`) because `#sum` and `#join` already have these built in, and it���s inconvenient to default each parameter to the empty array (e.g. `[(numbers || []).sum, (letters || []).join]`) if they���re used in multiple places. I usually solve the problem on entry to the block by making its parameters optional: ``` >> ary.transpose.then { |numbers = [], letters = []| [numbers.sum, letters.join] } => [0, ""] ``` This allows the body of the block to avoid having to deal with the possibility of `numbers` and `letters` being `nil`; I know that `#sum` and `#join` will give the correct results automatically. But it���s verbose, and sometimes I forget to do it, and [until recently](https://github.com/ruby/ruby/pull/8006) it didn���t work with YJIT. With the optional size argument to `#transpose` I only have to say how many array parameters the block expects: ``` >> ary.transpose(2).then { |numbers, letters| [numbers.sum, letters.join] } => [0, ""] ``` As well as being more concise, I think this is easier to get right every time. ---------------------------------------- Feature #19830: Allow `Array#transpose` to take an optional size argument https://bugs.ruby-lang.org/issues/19830#change-104081 * Author: tomstuart (Tom Stuart) * Status: Open * Priority: Normal ---------------------------------------- One benefit of supplying an initial value to `Enumerable#inject` is that it avoids an annoying edge case when the collection is empty: ``` >> [1, 2, 3].inject(:+) => 6 # good >> [].inject(:+) => nil # bad >> [].inject(0, :+) => 0 # good ``` A similar edge case exists for `Array#transpose`: ``` >> [[1, :a], [2, :b], [3, :c]].transpose => [[1, 2, 3], [:a, :b, :c]] # good >> [].transpose => [] # bad ``` Although no explicit `nil` is produced here, the subtle problem is that the caller may assume that the result array contains arrays, and that assumption leads to `nil`s in the empty case: ``` >> [[1, :a], [2, :b], [3, :c]].transpose.then { _2.join } => "abc" >> [].transpose.then { _2.join } undefined method `join' for nil:NilClass (NoMethodError) ``` If we allow `Array#transpose` to take an optional argument specifying the size of the result array, we can use this to always return an array of the correct size: ``` >> [[1, :a], [2, :b], [3, :c]].transpose(2) => [[1, 2, 3], [:a, :b, :c]] # good >> [].transpose(2) => [[], []] # good ``` By avoiding an unexpectedly empty result array, we also avoid unexpected downstream `nil`s: ``` >> [[1, :a], [2, :b], [3, :c]].transpose(2).then { _2.join } => "abc" >> [].transpose(2).then { _2.join } => "" ``` Here is a patch which adds an optional argument to `Array#transpose` to support the above usage: https://github.com/ruby/ruby/pull/8167 Something similar was requested eleven years ago in #6852. I believe this feature addresses the problem expressed in that issue without compromising backward compatibility with existing callers of #transpose. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/