From: david.n.arnold@...
Date: 2017-08-12T16:00:39+00:00
Subject: [ruby-core:82364] [Ruby trunk Bug#13795] Hash#select return type does not match Hash#find_all

Issue #13795 has been updated by davidarnold (David Arnold).


>  > Also, as a side note, it seems that Hash#select and #reject really are the only two methods that behave this way.  And even inside Hash, methods like #collect, #collect_concat, #drop, #drop_while, #grep, #max, #max_by, #min, #min_by, #sort, #sort_by, #take, and #take_while are all still returning Array.
>  
>  Let's analyze this a bit.
> [...]

I agree that all those methods are better in returning Array.  My pointing out the others wasn't meant to imply that they should be converted to returning Hashes.  My intention was to point out as a developer you have to remember that every Enumerable method in Hash returns an Array except select and reject.

Beyond the mental catch there, my strongest reason to offer for why those methods should also return Array is that any code receiving a Hash in its aspect of being an Enumerable will receive the *wrong type* per the Enumerable documentation.  That means that anything that uses these methods below will fail:

~~~ ruby
Array.instance_methods - Hash.instance_methods #=> [:join, :rotate, :rotate!, :sort!, :sort_by!, :collect!, :map!, :delete_at, :transpose, :fill, :uniq!, :shuffle!, :shuffle, :sample, :combination, :repeated_permutation, :permutation, :repeated_combination, :product, :flatten!, :bsearch_index, :bsearch, :&, :*, :+, :-, :pack, :|, :insert, :rindex, :<<, :reverse, :reverse!, :concat, :to_ary, :slice, :slice!, :at, :last, :push, :pop, :unshift, :each_index]
~~~


But, to my bug report, if there is some extremely strong reason why hashes should still be returned, then why was find_all left behind?

----------------------------------------
Bug #13795: Hash#select return type does not match Hash#find_all
https://bugs.ruby-lang.org/issues/13795#change-66161

* Author: davidarnold (David Arnold)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
* ruby -v: ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]
* Backport: 2.2: UNKNOWN, 2.3: UNKNOWN, 2.4: UNKNOWN
----------------------------------------
Enumerable#select and Enumerable#find_all are aliases.  Hash is_a Enumerable, yet only Hash#select was overridden to return a Hash, with Hash#find_all still returning an Array.  This is confusing since the message is that you can use select and find_all interchangeably for Enumerable, yet when you get to Hash, there are warnings that it is no longer true.  

Also any code that expects to call select on an Enumerable and get an array back (as documented) could break, but only for Hash#select.

Example:

~~~ ruby
def select_many(*enumerables, &block)
  result = []
  enumerables.each do |e|
    result.concat e.select(&block)
  end
  result
end

select_many([1, 2], [3, 4]) { |x| x % 2 == 0 } #=> [2, 4]

select_many({ 1 => 2 }, { 3 => 4 }) { |k, v| k < 2 } #=> TypeError: no implicit conversion of Hash into Array
~~~

Should Hash#find_all also return a Hash for consistency?  Or, given the fact that calling #to_h on the resulting Array is so easy, should Hash#select revert to the Enumerable behavior of returning an Array?

Proposal 1:

~~~ ruby
h = { "a" => 100, "b" => 200, "c" => 300 }
h.find_all {|k,v| k > "a"}  #=> {"b" => 200, "c" => 300}
~~~

Proposal 2:

~~~ ruby
h = { "a" => 100, "b" => 200, "c" => 300 }
h.select {|k,v| k > "a"} #=> [["b", 200], ["c", 300]]
h.select {|k,v| k > "a"}.to_h #=> {"b" => 200, "c" => 300}
~~~



-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>