3

Often when programming in Ruby, I find myself writing small for loops with a single statement in the body. For example...

for number in 1..10
  puts number
end

In other languages like C, Java or Kotlin (for example), I'd be able to write the same code in two lines. For example...

// Kotlin
for (number in 1..10)
    println(number)

In the above example, the ending of the loop body is inferred due to the lack of curly braces.

Does Ruby have a way to imitate this "single-statement" style of "for loop"?

Here are some of [my options/your potential replies], along with my thoughts on them.

  • You could append ; end to the body to end it on the same line.

    This is true, and pretty sufficient, but I'd like to know if there's a more idiomatic approach.

  • This seems unnecessarily picky. Why would you ever want to do this?

    You may think I'm being too picky. You may also think what I'm trying to do is un-idiomatic (if that's even a word). I totally understand, but I'd still love to know if it's do-able!

    Doing this could let us write code that's even just a tiny bit nicer to read. And for programmers, readability matters.

byxor
  • 5,930
  • 4
  • 27
  • 44
  • 2
    The infamous "goto fail" feature. – Stefan Dec 28 '16 at 09:50
  • @Stefan I think heartbleed had a similar issue with missing curly braces. The last company I worked for required them to be there. I like the shortness of excluding them though, and I do TDD so I'm very unlikely to get caught by an unexpected bug like that. – byxor Dec 28 '16 at 17:30

3 Answers3

8

Sure, you're looking for each, Range#each in this particular case:

(1..10).each { |number| puts number }

For more complex iterations use do - end block syntax. For example

(1..10).each do |number|
  puts number
  some_method_call(number)
  Rails.logger.info("The #{number} is used")
  something_else
end

To find more check out Ruby documentation, in particular, see Enumerable.

Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
  • This is quite nice, although I find it a little unusual to read. Will it appear more natural the more I use it? – byxor Dec 28 '16 at 03:21
  • 3
    @BrandonIbbotson Yes it will. The `for` loop really never needs to be used in ruby. – Eli Sadoff Dec 28 '16 at 03:21
  • 2
    @BrandonIbbotson absolutely. This is the conventional way to iterate over a collection in Ruby – Andrey Deineko Dec 28 '16 at 03:21
  • Perfect & thank you! I'll mark your answer as accepted after 10 minutes (assuming nobody else comes along and writes a better answer) ;) – byxor Dec 28 '16 at 03:22
  • After 10 minutes of using this, it actually looks extremely nice. – byxor Dec 28 '16 at 03:48
  • 1
    @BrandonIbbotson yep :) Also, use `{}` for one liner, if you need to do something more complex inside the block, use `do` `end` block, inside of which you can use new line to do new operation while being in the same iteration – Andrey Deineko Dec 28 '16 at 03:49
  • Thanks for a tip. I was aware there was a slight difference between the two kinds of blocks. – byxor Dec 28 '16 at 03:51
  • @AndreyDeineko: the __real__ idiomatic way to run a counter would be `10.times do`. Or `1.upto(10) do`. No need to involve a range here :) – Sergio Tulentsev Dec 28 '16 at 14:57
  • @AndreyDeineko: you can do multiline blocks with `{}`, no problem. In some cases, it's even preferred! There is a difference between `{}` and `do end`, but it's not that. :) – Sergio Tulentsev Dec 28 '16 at 14:58
  • @SergioTulentsev yea, both points are as usual very good - thanks! – Andrey Deineko Dec 28 '16 at 15:45
2

There is an even shorter syntax.

If you are just calling one method on each object you can use & syntax.

(1..3).collect(&:odd?) # => [true, false, true]

This is the same as

(1..3).collect { |each| each.odd? } # => [true, false, true]

This is the preferred way of writing loops in Ruby.

You'll quickly get used to both & and {} block syntax and the enumeration methods defined in Enumerable module. Some useful methods are

  • each which evaluates the block for each element
  • collect which create new array with the result from each block
  • detect which returns the first element for which block results true
  • select which create new array with elements for which block results true
  • inject which applies "folding" operation, eg sum = (1..10).inject { |a, b| a + b }

Fun fact, style guides for production code usually ban for loops at all because of a subtle but dangerous scoping issue. See more here, https://stackoverflow.com/a/41308451/24468

Community
  • 1
  • 1
akuhn
  • 27,477
  • 2
  • 76
  • 91
2

There is a vanishingly tiny number of cases, where any self-respecting Ruby programmer would even write an explicit loop at all. The number of cases where that loop is a for loop is exactly zero. There is no "more idiomatic" way to write a for loop, because for loops are non-idiomatic, period.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653