FirstEachLast, an extension to the Enumerable module.

K

Kristof Bastiaensen

Hi,

Hm, that's true. Although that argument would not stop me from advocating
#empty? Runtimes of each enumerable class differ anyway. Classes
override this method usually anyway. It's a different case with #size
which has been shown to not terminate for certain non totally esoteric
implementations.

Here's maybe another reason: emty? and size both depend on an Enumerable
with a deterministic amount of elements. This need not always be the
case:

class RandEnum
include Enumerable

def each
rand(10).times { yield rand 20 }
self
end
end

That's all nice and esoteric, but I don't see why it is a
reason against empty? or size. In fact, if that would be
important, than the other methods in Enumerable shouldn't
also be there. Take for example the following:

require "enumerator"
to_enum:)loop).map

There is nothing that prevents that. In fact I currently have
irb eating up slowly all the memory. Does that mean there
shouldn't be a map method? I think it is possible to find
pathological cases for the other methods in Enumerable
(I'll leave that as an exercise for the reader).

IMO the reason against size is that is not possible to implement
efficiently. As for empty?, I don't know. I find it useful,
and as shown by other people easy to implement.

Cheers,
KB
 
M

Martin DeMello

Ara.T.Howard said:
my concern would be for code that created a temporary data structure to
iterate - the designer should re-design! ;-) seriously, the rbtree package
from the raa works exactly like an ordered hash and it does not do this - i
would say it's a serious flaw if an ordered container cannot iterate
itself in order w/o creating temp structures - though your point is
well taken and correct.

class Foo
include Enumerable
def each
sort_by:)constantly_changing_criterion).each {|i| yield i}
end
end

But now that I think of it, there's nothing that says such a class
couldn't override empty?

martin
 
R

Robert Klemme

Kristof Bastiaensen said:
Hi,



That's all nice and esoteric, but I don't see why it is a
reason against empty? or size.

It is. Regard:

if enum.empty?
puts "it's empty"
else
enum.each {|x| puts "found #{x}"}
end

This code could not do what you expect because the properties empty? and
size would change between invocations. The guarantee, that each iteration
returns the same sequence of elements is just not part of the contract of
Enumerable.
In fact, if that would be
important, than the other methods in Enumerable shouldn't
also be there. Take for example the following:

require "enumerator"
to_enum:)loop).map

There is nothing that prevents that. In fact I currently have
irb eating up slowly all the memory. Does that mean there
shouldn't be a map method?

No. Same holds for recursion. But it's a different story:
I think it is possible to find
pathological cases for the other methods in Enumerable

RandomEnum is not a pathological case, it's a completely legitimate
implementation that conforms to all that is expected from #each. And
while these kinds of implementations are not widely used I still don't
regard them esoteric.

And infinite loop will always lead to problems, regardless whether it
appears due to endless recursion or as infinite iteration. So IMHO that's
not an argument agains any construct.
(I'll leave that as an exercise for the reader).

IMO the reason against size is that is not possible to implement
efficiently.

That's not an argument IMHO because classes including Enumerable most
likely override size and empty? with their own efficient implementations.
As for empty?, I don't know. I find it useful,
and as shown by other people easy to implement.

I've changed my mind here: the contract of Enumerable (or of #each if you
like) is too weak to guarantee a proper implementation of size and empty?.
The requirements on #each are really very weak and you can't provide a
proper size and empty? method for each classes using Enumerable. So IMHO
it's best to leave them out or put them in a separate module.

Kind regards

robert
 
R

Robert Klemme

John Carter said:
def empty?
each do |element|
return false
end
true
end

def size
inject(0) {|n,e| n+=1}
end

That's basically what i suggested earlier. Note though that you don't
need the |element| in empty? as well as "n+1" is sufficient in size.

But intermediately I changed my mind (see other thread).

robert
 
K

Kristof Bastiaensen

It is. Regard:

if enum.empty?
puts "it's empty"
else
enum.each {|x| puts "found #{x}"}
end

This code could not do what you expect because the properties empty? and
size would change between invocations. The guarantee, that each iteration
returns the same sequence of elements is just not part of the contract of
Enumerable.


No. Same holds for recursion. But it's a different story:


RandomEnum is not a pathological case, it's a completely legitimate
implementation that conforms to all that is expected from #each. And
while these kinds of implementations are not widely used I still don't
regard them esoteric.

And infinite loop will always lead to problems, regardless whether it
appears due to endless recursion or as infinite iteration. So IMHO that's
not an argument agains any construct.


That's not an argument IMHO because classes including Enumerable most
likely override size and empty? with their own efficient implementations.


I've changed my mind here: the contract of Enumerable (or of #each if you
like) is too weak to guarantee a proper implementation of size and empty?.
The requirements on #each are really very weak and you can't provide a
proper size and empty? method for each classes using Enumerable. So IMHO
it's best to leave them out or put them in a separate module.

Kind regards

robert

You are right. While thinking of the best example to support
my argument, I actually came up with an (non esoteric) example
that shows your point.
IO includes enumerable, and reading from an IO will change the IO.
For example:

require 'stringio'
io = StringIO.new <<EOS
first line
second line
third line
EOS

io.map unless io.empty
=> ["second line\n", "third line\n"]

#-----------------------

So I think that's really the reason why size and empty? is
not included.

Thanks,
KB
 
R

Robert Klemme

You are right.

Well, in fact *Matz* was right. I'm just a stupid guy that needed quite a
detour to reach this understanding of the matter. Thank you for helping
my on this! Once again learnes something new.
While thinking of the best example to support
my argument, I actually came up with an (non esoteric) example
that shows your point.
IO includes enumerable, and reading from an IO will change the IO.

Yeah, there are Enumerables that can yield their sequence only once.
For example:

require 'stringio'
io = StringIO.new <<EOS
first line
second line
third line
EOS

io.map unless io.empty
=> ["second line\n", "third line\n"]

Nice short example that has it all.
So I think that's really the reason why size and empty? is
not included.

So finally we reach consensus. :)

Thanks again!

robert
 
J

John Carter

Hehe, and how long does it take to determine the size of infinite or cyclic
Enumerable's? ;-)

Not nearly as long as Enumerable::sort does already...

John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email : (e-mail address removed)
New Zealand

The universe is absolutely plastered with the dashed lines exactly one
space long.
 
G

George Ogata

Robert Klemme said:
Kristof Bastiaensen said:
Hi,



That's all nice and esoteric, but I don't see why it is a
reason against empty? or size.

It is. Regard:

if enum.empty?
puts "it's empty"
else
enum.each {|x| puts "found #{x}"}
end

This code could not do what you expect because the properties empty? and
size would change between invocations. The guarantee, that each iteration
returns the same sequence of elements is just not part of the contract of
Enumerable.
[...]

RandomEnum is not a pathological case, it's a completely legitimate
implementation that conforms to all that is expected from #each. And
while these kinds of implementations are not widely used I still don't
regard them esoteric.
[...]

I've changed my mind here: the contract of Enumerable (or of #each if you
like) is too weak to guarantee a proper implementation of size and empty?.
The requirements on #each are really very weak and you can't provide a
proper size and empty? method for each classes using Enumerable. So IMHO
it's best to leave them out or put them in a separate module.

What's a "proper implementation of size"? Is include?, min, any?,
etc. implemented "properly" in these cases?

Why not just take the meaning of #size as "same as `to_a.size'
(assuming default #to_a)", or some such. Similar for #empty?,
#each_index, #index, #join, and anything else in Array that can
potentially be in Enumerable. This accomodates the common case, which
is what we should be striving for, isn't it?
 

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

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top