On 6/21/07, [email protected] [email protected] wrote:
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 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/