clonecq clonecq - 1 month ago 4
Ruby Question

Ruby: Ignore first input in a loop with this expression, help understand this expression

I needed a loop to read text line-by-line but completely ignore the first line. I come across this very handy expression posted online by Nobuyoshi Nakada. This expression skips one iteration of Enumerator. Can someone help to explain the last step in evaluation? Here is the example for illustration. This code will print 1 to 5 instead of 0 to 5. I am new to programming, I don't know if this a common solution. It took me 3 days to find it!

(0..5).each do |i|
if (first=true)..false and first
next
end
puts i
end


I attemplted to evaluate
if (first=true)..false and first
with
pry
but could not get deep enough.


  1. (first=true)
    evaluates to: true (value of
    first
    is true)

  2. (first=true)..false
    evaluates to: true..false (value of
    first
    is still true)

  3. (first=true)..false and first
    evaluates to: true..false and true, which is true and true (value of
    first
    is still true)

  4. if (first=true)..false and first
    evaluates to: if(true), executes next statement and exits if loop (value of
    first
    is now nil)

  5. Next iteration of Enumerator returns to
    if (first=true)..false and first
    expression (value of first is still nil)

  6. This step evaluates to false:
    if(first = true)..false and nil
    (value of
    first
    on the left side is true and on right side is nil(false)
    and I am lost hereā€¦



QUESTION: Why is
first
on the right side of if expression is not assigned "true". It seems that value of
first
is true and nil at the same time? Have I missed something here?

Answer

The flip-flop operator is a somewhat obscure feature of Ruby inherited from Perl (like most of Ruby's more obscure features). It is an expression that flips and flops back and forth between true and false.

Basically, it starts out as false, then it "flips" to true as soon as the left expression evaluates to a truthy object, and it "flops" back to false again as soon as the right expression evaluates to a truthy object.

So, the condition starts out as false. The first time through the loop, the left expression evaluates to a truthy value (in Ruby, assignments evaluate to the value that is being assigned) and as a side-effect also assigns true to first. Since the left expression evaluates to a truthy value, the flip-flop flips to true, and the entire condition now is true and true which is true. So, we execute the body of the conditional expression, which is simply next, i.e. skips the current iteration.

The second (and all the other times) through the loop, the flip-flop has already flipped to true, so now we only check the right expression for when the flip-flop should flop back to false again. The left expression is no longer checked. Well, the right expression is just false so we will never flop back to false again, the flip-flop will now always be true. However, the left expression will not be evaluated (we only check the right expression for when to flop back again), which means the assignment to first will never be executed, which means first is an un-initialized local variable, which means it evaluates to nil, which is falsy, which means that true and nil evaluates to false. And this will continue to be so for every iteration of the loop.

So, this code uses an obscure feature (the flip-flop) in an even more obscure manner (not as a conditional but simply to "disable" execution of the assignment). This is very definitely not code that any Rubyist would write. A much more idiomatic way would be

(0..5).drop(1).each do |i|
  puts i
end

or

(0..5).each.with_index do |n, idx|
  next if idx.zero?
  puts n
end

Note that nobu is a Ruby core developer, Japanese, and one of the earliest adopters of Ruby. The Japanese early adopters of Ruby are a pretty different sub-community from the larger Ruby community. It may very well be that this is actually a well-known and well-understood idiom among Japanese Rubyists and/or Ruby core developers. The thing about idioms is that if you know what they look like and you know what they mean, you don't need to understand them, so code like this may be okay, if it is idiomatic. But it certainly is not idiomatic in the larger Ruby community.

Comments