From: Rick DeNatale Date: 2007-06-22T07:08:59+09:00 Subject: Re: Behaviour of Enumerables reject vs. select mixed into Hash On 6/21/07, dblack@wobblini.net wrote: > Hi -- > > On Thu, 21 Jun 2007, Robert Dober wrote: > > > On 6/21/07, dblack@wobblini.net wrote: > >> > I fail to see why, could you give an example? > >> > >> Anything that depends on the output having array-like behaviors: > >> > >> selected = enum.select(&select_block).flatten > > That is ok, but we are complaining about > > > > hash.select > > > > Please note that the notion of Enumerable#select is erroneous in this > > thread, sorry for being blunt Alexander but you were talking about > > Hash#select the whole time. > > Yes, but making a special case of Hash, as opposed to other > enumerables, is exactly what breaks duck typing. I would actually > prefer to see Hash#reject return an array. It probably won't surprise you but I see it differently. I'd actually argue that having an inconsistency between select and reject in the same class is more harmful to duck-typing than either not returning an array. Forcing an array return actually is not using duck-typing. At the very least, it can be seen, as I and it seems Alexandar, Trans and Robert D do, as forcing a Hash to return an odd duck, or forcing a Chicken (the Hash) to give birth to a Duck (Array). The suggestion that Trans made that Enumerable respect the class of the Module it's included in is not a bad idea IMHO, with the proviso that it also needs to respect the meaning of the method as well, for example (1..5).reject {|i| i == 3} would have a hard time returning a Range. The way this issue was addressed in (ahem) Smalltalk, was to have a method named species which returned the class to be used for the result of methods like select, reject, collect. Here's a sketch of how this COULD be done in Ruby, I named the methods e_select and e_reject to avoid having Hash override. rick@frodo:/public/rubyscripts$ cat enumtest.rb module Enumerable def add_from_e_method(elem) self << elem end def species self.class end def e_select result = self.species.new each {|elem| result.add_from_e_method(elem) if yield elem} result end def e_reject(&blk) result = self.species.new each {|elem| result.add_from_e_method(elem) unless yield elem} result end end class Hash def add_from_e_method(elem) self[elem[0]] = elem[1] end end class Range def species Array end end h = {'foo' => 'bar', 'baz' => 'qux'} p h.e_select {|(k,v)| k == 'foo'} p h.e_reject {|(k,v)| k == 'foo'} p (1..10).e_select {|i| (3..4) == i} p (1..10).e_reject {|i| (3..4) == i} rick@frodo:/public/rubyscripts$ ruby enumtest.rb {"foo"=>"bar"} {"baz"=>"qux"} [] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] Of course, as they say God is in the details, but this should give an idea. Were this done in the base, there are probably other and more efficient ways to accomplish the same result. > > I think this would be a picture case for breaking backwards > > compatibility in Ruby2.0 > > Hash#select --> returning a Hash > > and we would live in a better world ;) > > unless you do not like it of course. > > > > In other words it might be interesting to know if people like this > > behavior without thinking about backwards compatibility or > > implementation details. > > I like having arrays be the "common currency" of select operations. I > don't think there's anything that you're prevented from doing as long > as that's the case. I know you do, it's consistent with the positions you took back when we were discussing to_splat. But I think that the case can be made that preserving the class of these methods is MORE not less duck-typed. I just don't buy that you can have any duck you want, as long as it's an Array is the only or even best choice. If ruby had a more duck-typed view of collections, it might lead to interesting things. Of course this is all really just food for thought. -- Rick DeNatale My blog on Ruby http://talklikeaduck.denhaven2.com/