for or each?

T

tekwiz

I just used the new roodi gem to check out some of my code that has a
lot of algorithmic code. It gave me a number of issues with the
phrase "Don't use 'for' loops. Use Enumerable.each instead." I prefer
for loops as opposed to using each simply because it's what I'm used
to coming from C-style languages.

Example:

This is what I do:

for i in 0...str.size
...
end

This is what roodi would have me do

(0...str.size).each do |i|
...
end

Is there a real, substantive reason to use each instead of for? Or is
it simply just a preference issue?

Thanks,
 
P

Phlip

tekwiz said:
This is what roodi would have me do

(0...str.size).each do |i|
...
end

Is there a real, substantive reason to use each instead of for? Or is
it simply just a preference issue?

It leaves you closer to a refactor to .map or .inject or .select or .reject or
..delete_if or .each_index or .each_with_index or ...
 
P

Phlip

It leaves you closer to a refactor to .map or .inject or .select or
.reject or .delete_if or .each_index or .each_with_index or ...

It also hints at:

str.each do |ch|

....
 
S

Sebastian Hungerecker

Phlip said:
IOW:

=A0 ... or .each_char or .each_line or .each_byte or ...

What?

Confused,
Sebastian
=2D-=20
Jabber: (e-mail address removed)
ICQ: 205544826
 
J

Joe Wölfel

It's interesting that array access using 'each' seems to be much
faster on my machine. In C, indexed-based for-loops are slow. It's
faster to increment pointers. Maybe it's similar under Ruby's hood.

ruby 1.86 (OS X PPC)
Rehearsal --------------------------------------------------
for loop 1.200000 0.000000 1.200000 ( 1.206533)
each 0.510000 0.000000 0.510000 ( 0.511994)
----------------------------------------- total: 1.710000sec

user system total real
for loop 1.190000 0.000000 1.190000 ( 1.190023)
each 0.500000 0.000000 0.500000 ( 0.508636)


ruby 1.9 (Same OS X PPC)
Rehearsal --------------------------------------------------
for loop 2.370000 0.010000 2.380000 ( 2.376402)
each 1.770000 0.000000 1.770000 ( 1.775798)
----------------------------------------- total: 4.150000sec

user system total real
for loop 2.310000 0.010000 2.320000 ( 2.316495)
each 1.780000 0.000000 1.780000 ( 1.775958)

Joe
 
P

Phlip

So, it's a code-readability issue and not a functional or complexity
issue?

'for' is arguably more readable. And it's not a performance issue - I suspect
the opcodes will be the same. It is very much a technical issue.

Good code is readable, minimal, and maintainable. Maintaining code requires
adding new features. Code should always be as ready for change as possible, so
much of our design rules (such as "object orientation") are really rubrics for
improving the odds that the next change comes easy.

This is an easier change...

array.each{|x| ... } -> array.map{|x| ... }

....than this:

for x in array ... -> array.map{|x| ... }

Further, your original example was very C-like. The iteration variable was the
array's index. Most iteration directly addresses each array's element, without
regard to its index. So 'for i in 0...str.size' is often more excessive than
'for x in str'.

Using .each leads to the correct mindset. Put another way, 'for' is an obsolete
concept - a legacy of languages without true iteration.
 
X

Xavier Noria

I don't think the minimal editing distance between #each and #map and
friends has anything to do. It is so unlikely that an #each becomes an
#inject that I don't think that's a good explanation for why people
prefer it over for. If <<some code>> becomes an #inject you just go an
edit whatever you need. Nah I don't think so.

The analogous for loop is written like this

for item in collection
# do something with item
end

I think #each has become the common for-idiom in Ruby because, you
know, the community has converged to that choice by themselves. That's
not very tangible, and perhaps may be due to the fact that blocks are
ubiquitous in Ruby and #each is syntactically closer in your head to a
lot of other stuff in Ruby.

I'd say #each is not that much favoured in ERb templates.
 
P

Phlip

Joe said:
It's interesting that array access using 'each' seems to be much
faster on my machine.

Does 'for' reevaluate its range after each tick? That would give 'for' a single
technical advantage over .each, in the very rare chance you need that.

Otherwise, where does the time go?

A quick experiment with RubyNode just showed Ruby generates different opcodes
for 'each' and 'for'. (By contrast, the notorious ternary operator, ? :,
generates the same opcodes as an equivalent 'if then else end' construction.)
 
J

Joe Wölfel

You can try my test if you like. I haven't checked it carefully. =20
But it does seem like 'each' is much faster under both ruby 1.86 and =20
ruby 1.9. The code is below.


require 'rubygems'
require 'benchmark'

a =3D (1..10000).to_a
Benchmark.bmbm 15 do |bench|

bench.report "for loop" do
x =3D 0
100.times do
for i in 0...(a.size)
x +=3D a
end
end=09
end
=09
bench.report "each" do
x =3D 0
100.times do
a.each do |i|
x +=3D i
end
end
end
=09
end

Joe
 
D

David Masover

=20
So, it's a code-readability issue and not a functional or complexity
issue?

These things are not entirely separate -- readable code is more likely to b=
e=20
functional and maintainable.

Or, maybe a better way of saying it is, the code should not merely be=20
readable, it should be expressing your intent.

Each is far more abstract than for. Take a simple array:

a =3D ['one','two','three']
for i in 0...a.size
puts "Give me a #{a}"
end

That's more prone to not work, as there are more visible moving parts, whic=
h=20
means more for you to think about, and more that can go wrong -- you might=
=20
type the wrong variable name in the a, for example, or type 0..a.size=20
instead of 0...a.size.

It also doesn't express your intent. You don't really need to know or care=
=20
where you are in that array, in this particular algorithm. You only need to=
=20
know which item you're going to print right now. So:

['one','two','three'].each do |x|
puts "Give me a #{x}"
end

Shorter, more readable (to me), quicker to type, and has the added benefit=
=20
that in the above example, that array falls out of scope as soon as the loo=
p=20
ends, so it can be collected sooner.

It also gives you a bit more flexibility. Suppose you're iterating over=20
something that someone passed in -- that means that I have to pass in=20
something that behaves like an array. It needs to have an accurate [] metho=
d,=20
probably supporting random access, even if you'll only access it=20
sequentially. It needs to have a length method, etc.

Which makes things quite a bit more awkward. What if I'm reading lines from=
a=20
file? Your way would force me to count every line in the file before I even=
=20
get started. What if it's a complex computation, like all the primes less=20
than a given number? I have to calculate them all out ahead of time, and=20
either store that array (wasting memory), or recalculate them from the=20
beginning.

There's more to it, of course -- you could imagine an each method which run=
s=20
in parallel threads, and I'm sure someone has written such a thing.

None of these will apply to every situation. It's entirely possible it's an=
=20
internal data structure. Even internal data structures could benefit from=20
some flexibility, but maybe you'll never touch this algorithm again.

But then, I don't really see a downside to doing it with 'each', instead=20
of 'for', other than that 'for' is what you're used to.
 
P

Phlip

Joe said:
You can try my test if you like. I haven't checked it carefully.
But it does seem like 'each' is much faster under both ruby 1.86 and
ruby 1.9. The code is below.

I just experimented with 'for' and found it does not reevaluate its header each
time it runs. That's the only thing that could have explained the time
difference, so maybe Matz & Co. have simply neglected 'for' while optimizing
..each, which everyone uses. (Though it's still technically superior; not just an
/ad populum/ thing...)

Another reason to use .each is your collection-like class might override it to
do something cool...
 
J

Joe Wölfel

Philip, I hadn't thought about your refactoring arguments. I think =20
I'm swayed by them. I do change an each to a map, etc., on =20
occasion. Also, it seems simpler and less confusing to have one =20
simple grammatical construction that does so many things.
 
X

Xavier Noria

You cannot rationalize a convention. Conventions happen, it is
difficult to explain why things are the way they are when they are
mostly stylistic.

You use two spaces in Ruby because you want your code to be idiomatic.
Can it be said that two spaces are obviously better than four or
eight? I don't think so, it is just a convention. And when you write
Perl or Java you use four. That's it.

In my opinion you use #each in your Ruby code because that's what
people use. That's what the book you first read use, that's what
everybody writes. Your code is supposed to use #each, you learn that
when you learn Ruby and probably *force a change in your mind a
priori* if you come from almost any other language. Just to follow the
conventions and write code that resembles what the community has
converged into.

You can argue that the convention has converged because iterators blah
and yielding blah, but

for user in users
...
end

is crystal clear, readable, has all the benefits of #each because it
uses #each, and what not.

As a counterargument, in ERb templates for-loops are not perceived as
"funny" and they are commonly used.
 
W

_why

It's interesting that array access using 'each' seems to be much faster on
my machine. In C, indexed-based for-loops are slow. It's faster to
increment pointers. Maybe it's similar under Ruby's hood.

Actually for loops are faster than `each`. Since it doesn't
introduce a block, there's no extra scope created. Not much faster,
but they are used in the computer language shootout, for instance.

You folks can argue all you want about the look of the `for` but
you're forgetting the utility of having two nice choices. One which
creates scope and one that doesn't. Don't let this Roodi lib boss
you around! You can make up your own mind about things.

_why
 
P

Phlip

_why said:
You folks can argue all you want about the look of the `for` but
you're forgetting the utility of having two nice choices.

Tx that's why I said 'for' can be more readable - even though I know nobody in
person aware of its existence.
One which
creates scope and one that doesn't. Don't let this Roodi lib boss
you around! You can make up your own mind about things.

Classic 'lint' comes with switches to STFU some warnings, thus making others
more useful...
 
J

Joe Wölfel

[Note: parts of this message were removed to make it a legal post.]


Actually for loops are faster than `each`. Since it doesn't
introduce a block, there's no extra scope created. Not much faster,
but they are used in the computer language shootout, for instance.
_why

Any thoughts on the Ruby 1.9 result? It seems like 'each' is much
faster than either the original for loop we started talking about, or
the non-index based one (for loop 2). You can run the code below if
you like.


Ruby 1.9 output:
Rehearsal --------------------------------------------------
for loop 1.240000 0.000000 1.240000 ( 1.240751)
for loop 2 1.180000 0.000000 1.180000 ( 1.176560)
each 0.510000 0.010000 0.520000 ( 0.512496)
----------------------------------------- total: 2.940000sec

user system total real
for loop 1.190000 0.010000 1.200000 ( 1.197695)
for loop 2 1.180000 0.000000 1.180000 ( 1.177620)
each 0.510000 0.000000 0.510000 ( 0.508858)

Ruby 1.86 output:
Rehearsal --------------------------------------------------
for loop 2.840000 0.040000 2.880000 ( 2.880437)
for loop 2 1.660000 0.000000 1.660000 ( 1.661229)
each 1.750000 0.010000 1.760000 ( 1.755502)
----------------------------------------- total: 6.300000sec

user system total real
for loop 2.300000 0.000000 2.300000 ( 2.307566)
for loop 2 1.660000 0.010000 1.670000 ( 1.666218)
each 1.760000 0.000000 1.760000 ( 1.760356)


require 'rubygems'
require 'benchmark'

a = (1..10000).to_a
Benchmark.bmbm 15 do |bench|

bench.report "for loop" do
x = 0
100.times do
for i in 0...(a.size)
x += a
end
end
end

bench.report "for loop 2" do
x = 0
100.times do
for i in a
x += i
end
end
end

bench.report "each" do
x = 0
100.times do
a.each do |i|
x += i
end
end
end
end
 

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
474,174
Messages
2,570,940
Members
47,484
Latest member
JackRichard

Latest Threads

Top