Behaviour of Enumerables reject vs. select mixed into Hash


T

Trans

Alexander I agree with your POV, however maybe it would do this
discussion some good to take one step back and look at the greater
picture.

Enumerable is a Mixin based on each and it implements methods like
select, map etc by using the each interface of it's client class. It
very often returns arrays than as it simply cannot do something
different - without creating a revolutionary new concept like an
Iterator but that is OffTopic

You were complaining about Hash#select returning an Array. I complain
about that too but it has nothing to do with Enumerable, Enumerable is
not used, it is implemented in Hash itself.

If we want to discuss if we like or dislike Hash#select returning an
array we shall fear to mention Enumerable as it has nothing to do with
it.
As a matter of fact it would be ridiculous - you have explained that
nicely yourself - to ask Enumerable to return Hashes.

Cheers
Robert

I can think of two potential solutions to this issue. Either require a
method in the enumerated class that dictates how to construct elements
of that class when enumerated (#<< might do), or we could have two
separate sets of enumerable methods, one for array elements and one
for hash key-value pairs.

In the first case:

(Note: I'm not testing this, so forgive any bugs. I just want to
convey the idea).

module Enumerable
def select(&blk)
o = self.class.new
each{|*e| o << e if blk[*e]}
end
end

class Hash
def <<(e)
self[e[0]] = e[1]
end
end

The downside here is, it is less efficient and breaks backward
compatibility.

The other option would require an #each_assoc method (maybe assoc
isn't the best term, but anyhow...)

module Enumerable
def select_assoc(&blk)
h = {}
each_assoc{|k,v| h[k]=v if blk[k,v]}
h
end
end

The downside here of course, is twice the number of Enumerable
methods.

T.
 
Ad

Advertisements

A

Alexander Presber

I can think of two potential solutions to this issue. Either require a
method in the enumerated class that dictates how to construct elements
of that class when enumerated (#<< might do), or we could have two
separate sets of enumerable methods, one for array elements and one
for hash key-value pairs.

In the first case:

(Note: I'm not testing this, so forgive any bugs. I just want to
convey the idea).

module Enumerable
def select(&blk)
o = self.class.new
each{|*e| o << e if blk[*e]}
end
end

class Hash
def <<(e)
self[e[0]] = e[1]
end
end

The downside here is, it is less efficient and breaks backward
compatibility.

I agree. And the implementation of << would be the
But the real solution can only be




The other option would require an #each_assoc method (maybe assoc
isn't the best term, but anyhow...)

module Enumerable
def select_assoc(&blk)
h = {}
each_assoc{|k,v| h[k]=v if blk[k,v]}
h
end
end

The downside here of course, is twice the number of Enumerable
methods.

T.
 
D

dblack

Hi --

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.

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.

David

The inconsistency of return values between Hash#reject vs. Hash#select
has bothered me for a long time. Above all, I'd want this to be
consistent, but unlike you (and I believe like the majority), I'd like
both to return a Hash.

I'm not really sure how duck typing even enters into it as a serious
concern. The blocks given to reject/select are different based on the
Enumerable type. Consider:

irb(main):017:0> %w{red green blue}.select { |elem| elem.length > 4 }
=> ["green"]
irb(main):018:0> {:red => true, :green => true, :blue => true}.select
{ |elem| elem.length > 4 }
(irb):18: warning: multiple values for a block parameter (2 for 1)
from (irb):18
(irb):18: warning: multiple values for a block parameter (2 for 1)
from (irb):18
(irb):18: warning: multiple values for a block parameter (2 for 1)
from (irb):18
=> []

I get an Array from both, which is wonderful if I'm expecting an Array
and I want to flatten the return value or do any other Array-specific
things. The only problem is one array is useless and wrong.

Didn't you like my evasive use of "&select_block"? :)


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
A

Alexander Presber

I can think of two potential solutions to this issue. Either require a
method in the enumerated class that dictates how to construct elements
of that class when enumerated (#<< might do), or we could have two
separate sets of enumerable methods, one for array elements and one
for hash key-value pairs.

In the first case:

(Note: I'm not testing this, so forgive any bugs. I just want to
convey the idea).

module Enumerable
def select(&blk)
o = self.class.new
each{|*e| o << e if blk[*e]}
end
end

class Hash
def <<(e)
self[e[0]] = e[1]
end
end

I agree, that seems to solve the problem very elegantly.
The implementation of << would be the second precondition for the
possibility to mix Enumerable in (the first being implementing each).
It allows for all "filter" methods to return a filtered version of
the original object, class and all.

This solution is general and provides for arbitrary future classes to
mix in Enumeration.
The downside here is, it is less efficient and breaks backward
compatibility.

The other option would require an #each_assoc method (maybe assoc
isn't the best term, but anyhow...)

module Enumerable
def select_assoc(&blk)
h = {}
each_assoc{|k,v| h[k]=v if blk[k,v]}
h
end
end

The downside here of course, is twice the number of Enumerable
methods.

Or n times, for n classes that mix in Enumerable. That sounds bad, imho.

Sincerely yours,
Alex
 
R

Robert Dober

Alexander I agree with your POV, however maybe it would do this
discussion some good to take one step back and look at the greater
picture.

Enumerable is a Mixin based on each and it implements methods like
select, map etc by using the each interface of it's client class. It
very often returns arrays than as it simply cannot do something
different - without creating a revolutionary new concept like an
Iterator but that is OffTopic

You were complaining about Hash#select returning an Array. I complain
about that too but it has nothing to do with Enumerable, Enumerable is
not used, it is implemented in Hash itself.

If we want to discuss if we like or dislike Hash#select returning an
array we shall fear to mention Enumerable as it has nothing to do with
it.
As a matter of fact it would be ridiculous - you have explained that
nicely yourself - to ask Enumerable to return Hashes.

Cheers
Robert

I can think of two potential solutions to this issue. Either require a
method in the enumerated class that dictates how to construct elements
of that class when enumerated (#<< might do), or we could have two
separate sets of enumerable methods, one for array elements and one
for hash key-value pairs.

In the first case:

(Note: I'm not testing this, so forgive any bugs. I just want to
convey the idea).

module Enumerable
def select(&blk)
o = self.class.new
each{|*e| o << e if blk[*e]}
end
end

class Hash
def <<(e)
self[e[0]] = e[1]
end
end

The downside here is, it is less efficient and breaks backward
compatibility.
It is a conceptional beauty but it really sucks for performance, there
is *no* solution to our problem that is backward compatible, we
explicitly ask for backward compatibility unless we go for a choosable
Enum Mixin.

Something like
class Hash
include TomsEnum
end
The other option would require an #each_assoc method (maybe assoc
isn't the best term, but anyhow...)

module Enumerable
def select_assoc(&blk)
h = {}
each_assoc{|k,v| h[k]=v if blk[k,v]}
h
end
end

The downside here of course, is twice the number of Enumerable
methods.
And although I cannot imagine a case, how do we know that there are
not Enumerables that take three or fourtytwo params ;).
After all Enumerable is a Mixin and we have to be prepared that it be
mixed in, right?

Robert
 
R

Robert Dober

is *no* solution to our problem that is backward compatible, we
explicitly ask for backward compatibility unless we go for a choosable
make this *incompatibility* please
 
Ad

Advertisements

A

Alexander Presber

module Enumerable
def select(&blk)
o = self.class.new
each{|*e| o << e if blk[*e]}
end
end

class Hash
def <<(e)
self[e[0]] = e[1]
end
end

The downside here is, it is less efficient and breaks backward
compatibility.

It is a conceptional beauty but it really sucks for performance

Why is that? Isn't the current implementation doing something
similar, but more like

def select(&blk)
o = new Array
each{|*e| o << e if blk[*e]}
end
there is *no* solution to our problem that is backward compatible, we
explicitly ask for backward compatibility unless we go for a choosable
Enum Mixin.
Something like
class Hash
include TomsEnum
end

Obviously breaking compatibilty is a very bad thing.
But without knowing for sure, I imagine Enumerables implementation
should have been something more along the lines of the above from the
beginning.
(I know, this is a bold claim and I'd like to see more opinions on
that. But transfires approach is exactly what I was thinking of when
bringing this up.)

Making Enumerable behave more agnostic to the class it is mixed in
(by letting the class itself provide a method to add an "element" to
an instance of itself, Enumerable becomes truly mixable into anything
that provides "each" and "<<".
Then doing reject on any class that mixes in Enumerable will yield a
filtered instance of that class, not Array.

That said - I think there should be no such thing as TomsEnum or any
special implementation.
Enumerable is the place to define methods for all things containing
enumerable elements.
The other option would require an #each_assoc method (maybe assoc
isn't the best term, but anyhow...)

module Enumerable
def select_assoc(&blk)
h = {}
each_assoc{|k,v| h[k]=v if blk[k,v]}
h
end
end

The downside here of course, is twice the number of Enumerable
methods.
And although I cannot imagine a case, how do we know that there are
not Enumerables that take three or fourtytwo params ;).
After all Enumerable is a Mixin and we have to be prepared that it be
mixed in, right?

Yes, one could impossibly provide for all possible Mixees like this.

Yours,
Alex
 
T

Todd Benson

Obviously breaking compatibilty is a very bad thing.
But without knowing for sure, I imagine Enumerables implementation
should have been something more along the lines of the above from the
beginning.
(I know, this is a bold claim and I'd like to see more opinions on
that. But transfires approach is exactly what I was thinking of when
bringing this up.)

Making Enumerable behave more agnostic to the class it is mixed in
(by letting the class itself provide a method to add an "element" to
an instance of itself, Enumerable becomes truly mixable into anything
that provides "each" and "<<".
Then doing reject on any class that mixes in Enumerable will yield a
filtered instance of that class, not Array.

That said - I think there should be no such thing as TomsEnum or any
special implementation.
Enumerable is the place to define methods for all things containing
enumerable elements.
The other option would require an #each_assoc method (maybe assoc
isn't the best term, but anyhow...)

module Enumerable
def select_assoc(&blk)
h = {}
each_assoc{|k,v| h[k]=v if blk[k,v]}
h
end
end

The downside here of course, is twice the number of Enumerable
methods.
And although I cannot imagine a case, how do we know that there are
not Enumerables that take three or fourtytwo params ;).
After all Enumerable is a Mixin and we have to be prepared that it be
mixed in, right?

Yes, one could impossibly provide for all possible Mixees like this.

Yours,
Alex

A duck is a bird. It doesn't behave exactly like every other bird you
know about. But you can be relatively certain it has wings. I
suspect the least common denominator return of the object is there for
several reasons, including testing, ease of the ruby language
development, etc. Why the difference for the Hash returning a
different object between select and reject? I think it's one of those
oversight things.

Thinking about LCD another way, why is it that "13s".to_i gives me 13?
Because it _mostly_ is an integer. So, if I have an Enumerable
object that I perform some operation on; where does it say in
duck-typing that I should get an Enumerable object back?

Just by its very nature, duck-typing will forever be laden with
inconsistencies. The LCD object return is a simple way around it. I
agree with the general consensus here that #select and #reject should
return the same way for a Hash, but you have to keep in mind that
writing a pragmatic language that does what you want is like trying to
assume everything about you as a programmer before you start typing.
To do that, maybe the designer should be using the LCD for pretty much
everything :)

Todd
 
A

Alexander Presber

Am 21.06.2007 um 19:31 schrieb Todd Benson:
A duck is a bird. It doesn't behave exactly like every other bird you
know about. But you can be relatively certain it has wings.

I am on your side up to here :)
I suspect the least common denominator return of the object is
there for
several reasons, including testing, ease of the ruby language
development, etc. Why the difference for the Hash returning a
different object between select and reject? I think it's one of those
oversight things.

Thinking about LCD another way, why is it that "13s".to_i gives me 13?

13 is a good result for "13s".to_i, I couldn't come up with a better
one: You ask the string class to
give you an integer (hence the _i) for a certain string and it does
so in an (arguably) optimal way.

However, [["baz", "qux"]] is not a good result for
{'foo' => 'bar', 'baz' => 'qux'}.select{|k,v| k=='baz' },
{'baz' => 'qux'} would be much more sensible.

Can you see the difference?
Because it _mostly_ is an integer. So, if I have an Enumerable
object that I perform some operation on; where does it say in
duck-typing that I should get an Enumerable object back?

It is not duck-typing promising me an enumerable.
When calling enum.to_a I _expect_ an Array and nothing else.

It is the _concept_ of "select" that promises a Hash when calling it
on a Hash.
Enumerability (and therefore the possibility to _iterate_) just
happens to be a requirement for selecting.

But as Yossef pointed out already, it doesn't have to do all that
much with duck typing.
Duck typing allows me to let a class share methods with other classes
(provided some conditions on these classes are met)
without specifying the exact classes beforehand.
A la: This is an "enumerable", it must be able to "select" some of
its elements with a specified rule.

You seem to imply, that in order to allow for classes to mix
Enumerable in, we would have to accept
to get Arrays back for filter operations as a "least common
denominator".

I disagree, and in fact that is the whole point of my first post.

By adding a second requirement to the class that wants to mix in
Enumerable (implementing an "appendElement" method or, as Array calls
it already "<<")
we can let go of that arbitrary and confusing "LCD": Any filtering
method returns the class of the object that gets filtered.
Just by its very nature, duck-typing will forever be laden with
inconsistencies.

I do not think so.
The LCD object return is a simple way around it. I
agree with the general consensus here that #select and #reject should
return the same way for a Hash, but you have to keep in mind that
writing a pragmatic language that does what you want is like trying to
assume everything about you as a programmer before you start typing.
To do that, maybe the designer should be using the LCD for pretty much
everything :)

I don not agree. I have not yet heard of the concept of "LCD" being
necessary to work with duck typing.

Sincerely yours,
Alex
 
R

Rob Biedenharn

...
Making Enumerable behave more agnostic to the class it is mixed in
(by letting the class itself provide a method to add an "element" to
an instance of itself, Enumerable becomes truly mixable into anything
that provides "each" and "<<".
Then doing reject on any class that mixes in Enumerable will yield a
filtered instance of that class, not Array.

That said - I think there should be no such thing as TomsEnum or any
special implementation.
Enumerable is the place to define methods for all things containing
enumerable elements.
The other option would require an #each_assoc method (maybe assoc
isn't the best term, but anyhow...)

module Enumerable
def select_assoc(&blk)
h = {}
each_assoc{|k,v| h[k]=v if blk[k,v]}
h
end
end

The downside here of course, is twice the number of Enumerable
methods.
And although I cannot imagine a case, how do we know that there are
not Enumerables that take three or fourtytwo params ;).
After all Enumerable is a Mixin and we have to be prepared that it be
mixed in, right?

Yes, one could impossibly provide for all possible Mixees like this.

Yours,
Alex

A duck is a bird. It doesn't behave exactly like every other bird you
know about. But you can be relatively certain it has wings. I
suspect the least common denominator return of the object is there for
several reasons, including testing, ease of the ruby language
development, etc. Why the difference for the Hash returning a
different object between select and reject? I think it's one of those
oversight things.

...

Todd

What no one seems to have (directly) focussed on is that Hash#each
gives two-element [k,v] arrays back as the content. Is this what
Alexander Presber means with:

It's how you define the elements that are enumerable. Hash#each_key,
Hash#each_value, and Hash#each_pair make this explicit. Hash#each
being simply Hash#each_pair (the documentation says it's the other
way around, but if they're synonyms what does it matter) makes the
definition of a "pair" or more explicitly "key-value pair" a good
place to focus.

Perhaps if a Hash was a collection of "entries" so #each returned
something closer to {k=>v} rather than an array.

s = [1, 2, 3]
r = s.class.new
s.each {|e| r << e}
r
=> [1, 2, 3]

Currently, if we replace the value of the array s with a hash { 1 =>
'uno', 2 => 'dos', 3 => 'tres' }
you'd need something like:

class Hash
alias_method :<<, :update
alias_method :eek:rig_each, :each
def each
orig_each {|k,v| yield({k=>v}) }
end
end

to get a similar result:

s={1=>'uno',2=>'dos',3=>'tres'}
r = s.class.new
s.each {|e| r << e}
r
=> {1=>"uno", 2=>"dos", 3=>"tres"}

if the notation to "unpack" block arguments .each {|(k,v)| ... }
would pick apart a hash entry like {k=>v} the same way that it would
[k,v] (which, of course, doesn't actually need the parentheses), then
the "syntactic compatibility" would be close enough for my brain,
eyes, and fingers. I think that the arity of block that each_pair
expects should remain 2 and each_pair should yield([k,v]) (so the
meaning of "pair" for English-speakers is maintained -- I realize I
being anglophilic here). The arity of the block expected by #each
would be 1.

It seems to me that you'd also get "benefits" like.

{1=>"uno", 2=>"dos", 3=>"tres"}.sort_by {|k,v| v}
=> [{2=>"dos"}, {3=>"tres"}, {1=>"uno"}]

Since you'd clearly need to transform to a type that was ordered if
you wanted to "sort" an unordered Hash.

And if there were a #<=> defined for a Hash entry, you could just use
#sort.

This is clearly a slippery slope because you'd then have to redefine
Hash#shift to give {k=>v} rather than [k,v] also. You'd almost
certainly want something like #first and #last to give the key and
value from {k=>v} like they'd pull those parts from [k,v].

[Blah! I took too long to answer and Alex(ander) got another
response to Todd in with some of the same ideas, but I decided to
post anyway.]

-Rob

Rob Biedenharn http://agileconsultingllc.com
(e-mail address removed)
 
T

Todd Benson

Am 21.06.2007 um 19:31 schrieb Todd Benson:
A duck is a bird. It doesn't behave exactly like every other bird you
know about. But you can be relatively certain it has wings.

I am on your side up to here :)
I suspect the least common denominator return of the object is
there for
several reasons, including testing, ease of the ruby language
development, etc. Why the difference for the Hash returning a
different object between select and reject? I think it's one of those
oversight things.

Thinking about LCD another way, why is it that "13s".to_i gives me 13?

13 is a good result for "13s".to_i, I couldn't come up with a better
one: You ask the string class to
give you an integer (hence the _i) for a certain string and it does
so in an (arguably) optimal way.

However, [["baz", "qux"]] is not a good result for
{'foo' => 'bar', 'baz' => 'qux'}.select{|k,v| k=='baz' },
{'baz' => 'qux'} would be much more sensible.

Can you see the difference?

Sure. Why does [1,2,3].delete(3) return the integer 3 instead of the
array? It's just semantics. What you think is a duck is not
necessarily what somebody else thinks is a duck.
It is not duck-typing promising me an enumerable.
When calling enum.to_a I _expect_ an Array and nothing else.

I don't know what you expect, but I think you expect some semblance of
continuity (in english).
It is the _concept_ of "select" that promises a Hash when calling it
on a Hash.

_concept_ of "select", again that's what you think it should mean.
Enumerability (and therefore the possibility to _iterate_) just
happens to be a requirement for selecting.

But as Yossef pointed out already, it doesn't have to do all that
much with duck typing.
Duck typing allows me to let a class share methods with other classes
(provided some conditions on these classes are met)
without specifying the exact classes beforehand.
A la: This is an "enumerable", it must be able to "select" some of
its elements with a specified rule.

You seem to imply, that in order to allow for classes to mix
Enumerable in, we would have to accept
to get Arrays back for filter operations as a "least common
denominator".

Okay, here's where you make a good point. Array actually is _not_ the
LCD of an Enumerable object logistically. But it may very well be the
LCD at the bottom level; the inner workings of Ruby. That's a bold
thing for me to say because I haven't read the Ruby source code yet.
I disagree, and in fact that is the whole point of my first post.

By adding a second requirement to the class that wants to mix in
Enumerable (implementing an "appendElement" method or, as Array calls
it already "<<")
we can let go of that arbitrary and confusing "LCD": Any filtering
method returns the class of the object that gets filtered.


I do not think so.

If you can have objects be inconsistent, your "pragmatic program"
means naught to me unless I already know where you're coming from --
cost of flexibility argument.
I don not agree. I have not yet heard of the concept of "LCD" being
necessary to work with duck typing.

I didn't say necessary.

I guess the point is moot because we seem to all agree that #select
and #reject should be polar opposites (I'm leaving out 3-valued logic
when I say that).

I was just pointing out that reject and select may not mean the same
thing to you as it does to me. When you don't know what the person
expects as a return value, you give them something that you yourself
might expect.

Todd
 
Ad

Advertisements

T

Todd Benson

What no one seems to have (directly) focussed on is that Hash#each
gives two-element [k,v] arrays back as the content.

Wow. Even though I've dealt with this all the time, that thought
didn't occur to me.
 
T

Trans

What no one seems to have (directly) focussed on is that Hash#each
gives two-element [k,v] arrays back as the content.

That's right. And if you wanted things to be "just ducky", it would
have to give only the v. I've actually argued in favor of that before,
b/c it's not unreasonable to see that an Array index is like a Hash
key. So, for a full parallel we'd need to see something like:

{:x=>'m'}.each { |v| v #=> 'm'

['m'].each { |v| v #=> 'm'

{'x'=>'m'}.each_assoc{ |a| a #=> ['x','m']

['m'].each_assoc{ |a| a #=> [0,'m']

One could easily argue that an Assoc class would be quite useful here,
rather than relying on 2-element Array to fulfill the roll. With that
in hand, it would re easy enough to add the #<< method for enumerable
construction.

T.
 
T

Todd Benson

What no one seems to have (directly) focussed on is that Hash#each
gives two-element [k,v] arrays back as the content.

That's right. And if you wanted things to be "just ducky", it would
have to give only the v. I've actually argued in favor of that before,
b/c it's not unreasonable to see that an Array index is like a Hash
key.

Regardless of what we think is the best, it's seems pretty clear to me
that the duplicate of the Hash is broken during a delete_if, reject,
select, etc. Thus, the array, and thus my comment about the array
being the LCD. Like I said, I haven't read the Ruby source, but I'm
starting to get curious enough to do so.
 
N

Nobuyoshi Nakada

Hi,

At Thu, 21 Jun 2007 23:45:42 +0900,
Trans wrote in [ruby-talk:256401]:
I can think of two potential solutions to this issue. Either require a
method in the enumerated class that dictates how to construct elements
of that class when enumerated (#<< might do), or we could have two
separate sets of enumerable methods, one for array elements and one
for hash key-value pairs.

Classes which use Enumerable are not only Array and Hash.
What do you expect for IO?
 
T

Trans

Hi,

At Thu, 21 Jun 2007 23:45:42 +0900,
Trans wrote in [ruby-talk:256401]:
I can think of two potential solutions to this issue. Either require a
method in the enumerated class that dictates how to construct elements
of that class when enumerated (#<< might do), or we could have two
separate sets of enumerable methods, one for array elements and one
for hash key-value pairs.

Classes which use Enumerable are not only Array and Hash.
What do you expect for IO?

Hmm...well for the first solution, I suppose we need a special
constructor to provide the kind of enumerable result we will be
building. In my example, I used self.class.new, by obviously that's
not always the case, so the class will need to tell us.

The second solution just has two forms of Enumerable, one which
returns an array and the other a hash. So #each_assoc might not alwasy
be defined, and thus these "hash-enumerable" methods wouldn't be
available. Or it would just deal in pairs, eg.

$stdin.select{ |s| s =~ /^x/ }
x123
a123
x890
bcde
#=> ["x123\n","x890\n"]

$stdin.select_assoc{ |k,v| k =~ /^x/ }
x123
a123
x890
bcde
#=> {"x123\n"=>"a123\n","x890\n"=>"bcde\n"]

T.
 
Ad

Advertisements

R

Rick DeNatale

Hi --



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.

[email protected]:/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}
[email protected]:/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.
 
D

dblack

Hi --

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.

Maybe, but I'll admit that at this stage in this thread, I'm more into
the concrete question of what harm is done by having array be the
common currency. (I'm having trouble following the duck-typing stuff
beyond a certain level of abstraction.) I guess it's just never
bothered me, which is probably an excessively home-spun way to put it
but it's about the most rigorous analysis I seem to be up to right now
:)

There's another thing, though, and that is the question of the
symmetry. I'm trying to put my figure on why it doesn't bother me
that Hash#reject returns a hash and Hash#select returns an array --
that is, I don't like the fact that Hash#reject is different from
other rejects (at least I somewhat don't), but in and of itself I
think the fact that reject is not simply defined as !select doesn't
bother me.

Maybe it's something like: rejecting means subtracting from a thing
that's already there, but selecting means creating a new collection --
which of course doesn't preclude having that collection be a hash, but
does mean that there's a threshold of deciding what the new collection
will, or could, be.

It would interesting if:

"abc\ndef\nghi".reject {|s| /abc/.match(s) }

resulted in "def\nghi"....


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
Ad

Advertisements

T

Todd Benson

There's another thing, though, and that is the question of the
symmetry.

I'm not really sure if I'm off-base here, but the first thing you
learn in set theory is that as soon as you see symmetry, you should be
skeptical. Why is that? Well, my cop-out answer is: let the
linguists in the group answer that.

It's pretty obvious from what everybody says about how Ruby works, and
how best to utilize it, is that it's a "lambda" ... a procedure that
just floats in. But it's also clear that it's unclear. It's a
contradiction.

We ultimately need expressibility in order to have flexibility And
your language of choice gives you a decidability set. I'm using 'ity'
a lot ... sorry.

Mr. David Black once again shows us an interesting perspective:

"Maybe it's something like: rejecting means subtracting from a thing
that's already there, but selecting means creating a new collection"

David is smart ... the linguists are dumb. Don't take offense guys :)

Todd
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top