## Feature #10498

### Make `loop` yield a counter

Status:
Open
Priority:
Normal
Target version:
-
[ruby-core:66220]

Description

# Problem¶

Teaching Ruby, we always end up with that type of construct

``````i = 0
loop do
i += 1
# do something with i....
raise StopIteration if i ...
end
``````

# Solution¶

What I propose with this patch is making `loop` yield the iteration count:

``````loop do |i|
# do something with i....
raise StopIteration if i ...
end
``````

`i` starts at 0 and stops at `FIXNUM_MAX` (there's no `Float::Infinity` equivalent for integers).

# Alternate solution¶

`Integer#times` could work if we had an `<Integer's infinity>` object, so we would just do `<Integer's Infinity>.times { |i| ... }`.

Also, this is the very first patch I submit to Ruby, I might have done something horrible, feel free to tell me :-)

Files

#### Updated by marcandre (Marc-Andre Lafortune)almost 6 years ago

I've also expected `loop` to yield a number and forget from time to time it doesn't.

Note you can achieve the same effect today with `(0..Float::INFINITY).each`. It's explicit, but quite a bit longer.

#### Updated by chrisseaton (Chris Seaton)almost 6 years ago

But doesn't this mean #loop will only run FIXNUM_MAX times? Rather than run infinitely as it currently does? That's a pretty big semantic change. Also, why not just overflow to Bignum?

#### Updated by marcandre (Marc-Andre Lafortune)almost 6 years ago

Chris Seaton wrote:

But doesn't this mean #loop will only run FIXNUM_MAX times?

Indeed, the patch is not acceptable.

Let's consider the feature request that loop infinitely and yields a number (that will eventually be a Bignum). If this is accepted, writing the patch won't be a big issue.

I forgot another existing alternative: `loop.with_index do |_, i|` gets the same effect.

#### Updated by duerst (Martin Dürst)almost 6 years ago

Shouldn't the version of loop that yields a number be called loop_with_index, to correspond with others such as each_with_index, map_with_index, and so forth? Maybe with a little bit of magic, that can be made to happen?

• Description updated (diff)

#### Updated by cesario (Franck Verrot)almost 6 years ago

Marc-Andre Lafortune wrote:

Chris Seaton wrote:

But doesn't this mean #loop will only run FIXNUM_MAX times?

Indeed, the patch is not acceptable.

This indeed was a mistake, I've re-submitted a new patch.

So now the counter starts at 0 and will eventually become a Bignum when bigger than FIXNUM_MAX.

#### Updated by phluid61 (Matthew Kerwin)almost 6 years ago

I agree with Martin, this should be Kernel#loop_with_index

#### Updated by cesario (Franck Verrot)almost 6 years ago

Martin Dürst wrote:

Shouldn't the version of loop that yields a number be called loop_with_index, to correspond with others such as each_with_index, map_with_index, and so forth? Maybe with a little bit of magic, that can be made to happen?

I might have overlooked them, but I can't find any reference to any other`*_with_index` method than `each_with_index`. Are they in Ruby core or in an external gem?

#### Updated by joffreyjaffeux (Joffrey Jaffeux)almost 6 years ago

Franck Verrot wrote:

I might have overlooked them, but I can't find any reference to any other`*_with_index` method than `each_with_index`. Are they in Ruby core or in an external gem?

`each_with_index` does exist, but concerning `map`, Martin was probably talking about `map.with_index` as defined in http://ruby-doc.org/core-2.1.5/Enumerator.html#method-i-with_index

Using `loop.with_index {|i| puts i}` will currently yield nil.

#### Updated by funny_falcon (Yura Sokolov)almost 6 years ago

```> loop.with_index.take(10)
=> [[nil, 0], [nil, 1], [nil, 2], [nil, 3], [nil, 4], [nil, 5], [nil, 6], [nil, 7], [nil, 8], [nil, 9]]
> loop.with_index{|_,i| p i; break}
0
=> nil
```

#### Updated by funny_falcon (Yura Sokolov)almost 6 years ago

```> LOOP = 2**1000
=> 10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376
> LOOP.times.take(10)
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> LOOP.times{|i| p i; break if i == 2}
0
1
2
=> nil
```

But still, if `loop` yields index it will not damage anyone.

But still, if `loop` yields index it will not damage anyone.

If existing code passes a lambda (or anything else with argument checking), it would damage them.

I think having loop yield a counter is very useful, but it should check `#arity != 0` for backwards compatibility.

#### Updated by funny_falcon (Yura Sokolov)almost 6 years ago

I think having loop yield a counter is very useful, but it should check #arity != 0 for backwards compatibility.

You are right. +1

#### Updated by znz (Kazuhiro NISHIYAMA)almost 6 years ago

How about `Numeric#step`?

```>> 0.step.take(3)
=> [0, 1, 2]
>> 1.step.take(3)
=> [1, 2, 3]
```

#### Updated by trans (Thomas Sawyer)almost 6 years ago

I always thought it would be most convenient if all loops had an intrinsic counter `\$i`.

#### Updated by rklemme (Robert Klemme)almost 6 years ago

I am actually against this feature. Reason: an infinite loop does not need a counter. We incur the cost of counting (especially when the figure leaves Fixnum space) on all infinite loops. For loops that have a fixed condition on the number (some have been shown with step take or other) that condition can be used upfront with a range or #step.

One can create a counting infinite loop like this:

```x = Enumerator::Generator.new {|y| i = 0; loop {y << i; i += 1}}
```

or even

```x = Enumerator::Generator.new {|y| i = -1; loop {y << (i += 1)}}
```

If needed that can be made a constant somewhere, e.g. in Enumerator.

#### Updated by cesario (Franck Verrot)almost 6 years ago

Hi Robert, thanks for taking time reading the ticket.

Robert Klemme wrote:

I am actually against this feature. Reason: an infinite loop does not need a counter.

Most examples I can find about it (and use personally when teaching) make use of a counter (all languages). Keeping track of the current iteration can be useful.

We incur the cost of counting (especially when the figure leaves Fixnum space) on all infinite loops.

I definitely agree . I wasn't able to find a way to fine-tune this based on the block's arity as mentioned previously.
On the other hand, you'd need 4,611,686,018,427,387,903 iterations before paying the price of using `BigNum`s, which compared to the code you're having in the block would probably be more expensive than incrementing a `BigNum`.
So I'm not sure whether or not that price should be considered high. Anything I'm missing?

```x = Enumerator::Generator.new {|y| i = -1; loop {y << (i += 1)}}
```

If needed that can be made a constant somewhere, e.g. in Enumerator.

Given the context, I'm afraid I wouldn't use this unless it's a well-known constant.

Thanks again, looking forward to more insights :-)

#### Updated by rklemme (Robert Klemme)almost 6 years ago

Franck Verrot wrote:

Hi Robert, thanks for taking time reading the ticket.

You're welcome!

Robert Klemme wrote:

I am actually against this feature. Reason: an infinite loop does not need a counter.

Most examples I can find about it (and use personally when teaching) make use of a counter (all languages). Keeping track of the current iteration can be useful.

"can"!

We incur the cost of counting (especially when the figure leaves Fixnum space) on all infinite loops.

I definitely agree . I wasn't able to find a way to fine-tune this based on the block's arity as mentioned previously.

That seems fairly easy:

```def lp(&b)

if b.arity == 0
while true
b[]
end
else
i = 0

while true
b[i]
i += 1
end
end

raise "This must never happen"
end
```

On the other hand, you'd need 4,611,686,018,427,387,903 iterations before paying the price of using `BigNum`s, which compared to the code you're having in the block would probably be more expensive than incrementing a `BigNum`.

Yes, but as a user of loop you would not have a choice any more. I'd rather use the approach from above or define another method loop_with_index or use the approach with Generator and a constant. I would definitively not unconditionally provide a counter.

So I'm not sure whether or not that price should be considered high. Anything I'm missing?

I think it is generally bad to do unnecessary work. Also it is inelegant. :-)

#### Updated by cesario (Franck Verrot)almost 6 years ago

Robert Klemme wrote:

[...] I wasn't able to find a way to fine-tune this based on the block's arity as mentioned previously.

That seems fairly easy:

```def lp(&b)

if b.arity == 0
while true
b[]
end
else
i = 0

while true
b[i]
i += 1
end
end

raise "This must never happen"
end
```

I meant in C but yes, given we could have `Kernel#loop_with_index` implemented in Ruby, it would be a no-brainer.

On the other hand, you'd need 4,611,686,018,427,387,903 iterations before paying the price of using `BigNum`s, which compared to the code you're having in the block would probably be more expensive than incrementing a `BigNum`.

Yes, but as a user of loop you would not have a choice any more. I'd rather use the approach from above or define another method loop_with_index or use the approach with Generator and a constant. I would definitively not unconditionally provide a counter.

`times` does yield a counter, would you say it's the same concern?

#### Updated by rklemme (Robert Klemme)almost 6 years ago

Franck Verrot wrote:

Robert Klemme wrote:

Yes, but as a user of loop you would not have a choice any more. I'd rather use the approach from above or define another method loop_with_index or use the approach with Generator and a constant. I would definitively not unconditionally provide a counter.

`times` does yield a counter, would you say it's the same concern?

No, because that is specifically designed for iterating a fixed number of times.

#### Updated by duerst (Martin Dürst)over 5 years ago

• Assignee changed from ruby-core to matz (Yukihiro Matsumoto)

#### Updated by naruse (Yui NARUSE)over 2 years ago

• Target version deleted (2.2.0)

Also available in: Atom PDF