This is part 3 of a series on Weird Ruby. Don’t miss Weird Ruby Part 1: The Beginning of the EndWeird Ruby Part 2: Exceptional Ensurance, and Weird Ruby Part 4: Code Pods (Blocks, Procs, and Lambdas).

Welcome back to Weird Ruby! This time we’re going to talk about Ruby’s rarely seen flip-flop operator and how you can use it to confuse and annoy future versions of yourself.

Hover boardHave you ever longed for a way to execute part of a loop part of the time? Do you also feel like if/else statements are too “clear” and “understandable” for your clever code? Then the flip-flop operator is perfect for you!

A flip-flop is a range operator that compares two conditions inside of a loop. It evaluates to true when the first condition is met until the second condition is met, after which it returns false.

Yes that’s about the best I can do, there isn’t a really easy way to explain this without showing you, so here we go:

Hoverboard shopping

Let’s say you’re out shopping for a hoverboard, and you don’t want to plunk down your credit card until you find one that’s totally rad. You’ve also decided that after finding a rad hoverboard if you see an ugly one you’re just going to stop caring. This is an ideal opportunity to do some flip-flops on your hoverboards.

hoverboards = [:blue, :pink, :rad, :lasers, :peuce, :ugly, :double_decker]

hoverboards_to_buy_maybe = []

 

hoverboards.each do |hoverboard|

  if (hoverboard == :rad)..(hoverboard == :ugly)

    hoverboards_to_buy_maybe << hoverboard

  end

end

So let’s start looping over our 7 hoverboards. On the first pass, hoverboard is :blue. Unfortunately :blue is not rad so the first conditional fails and we skip our if block; nothing gets added to hoverboards_to_buy_maybe.

(:blue == :rad)..(:blue == :ugly)

=> false
# hoverboards_to_buy_maybe: []

Next up we find the :pink hoverboard, and while arguably an improvement over that creepy :blue board we’re still not in rad territory, so we skip our if block again and move on.

(:pink == :rad)..(:pink == :ugly)

=> false
# hoverboards_to_buy_maybe: []

On the third pass through our loop we finally find our :rad hoverboard. The flip-flop evaluates to true and we shovel :rad into our hoverboards_to_buy_maybe list. Because we’ve now seen a :rad hoverboard our flip-flop operator will continue to return true until we meet the second condition.

(:rad == :rad)..(:rad == :ugly)

=> true
# hoverboards_to_buy_maybe: [:rad]

Next up is our fourth hoverboard, :lasers, and the flip-flop state is still true because we just met our first condition. The second :ugly condition is not met, because a hoverboard made of lasers is an unimaginably beautiful thing, so the flip-flop state doesn’t change. We haven’t met the second condition to turn off our flip-flop, so it stays true and we shovel :lasers right into hoverboards_to_buy_maybe.

(:lasers == :rad)..(:lasers == :ugly)

=> true
# hoverboards_to_buy_maybe: [:rad, :lasers]

Next up we find a :peuce hoverboard, a poorly defined color that is sometimes hideous and other times not, but the important thing to us now is that it’s not exactly :ugly. We shovel the :peuce board into our list and since we still haven’t found the :ugly board, our flip-flop remains true as we head into our sixth iteration.

(:peuce == :rad)..(:peuce == :ugly)

=> true
# hoverboards_to_buy_maybe: [:rad, :lasers, :peuce]

In our sixth pass we finally find our :ugly hoverboard and reach the absolute bottom of our bucket of cares. Since we’ve tripped our second ‘hoverboard == :ugly’ conditional our flip-flop is now set to false. We shovel this final monstrosity into our array and move on with our shopping.

(:ugly == :rad)..(:ugly == :ugly)

=> false
# hoverboards_to_buy_maybe: [:rad, :lasers, :peuce, :ugly]

In our seventh and final iteration we completely ignore the glory that is the :double_decker hoverboard, as we’ve now decided we don’t care. If this board happened to meet our first condition, our flip-flop would have gone back to true, we would have executed our if block and shoveled the :double_decker into hoverboards_to_buy_maybe. It’s probably for the best, double decker hoverboards sound incredibly dangerous.

When our loop is finished hoverboards_to_buy_maybe looks like this:

[:rad, :lasers, :peuce, :ugly]

To recap, we iterated until the first condition in our flip-flop was met, skipping the if block each time. Once we met the first condition, the flip-flop state became true, so we started adding things to our list. We continued iterating and executing our if block each time until we met the second condition, after which the flip-flop state was set to false and we transformed back into an apathetic lump.

Two-dot, three-dot, red-dot, blue-dot

The flip-flop operator itself is actually a range of conditionals, where the range only returns true or false: true once the first condition is met and until the second condition is met, then false until the first condition is met again.

As you may know there are two types of ranges in Ruby: the two-dot and the three-dot. In our example we’re using the two-dot version, which evaluates both of the conditionals for a given iteration. So if we meet our first condition and our second condition in a single pass, the flip-flop will finish the loop set to false, though it will execute the body of the if block exactly once.

To demonstrate let’s change our flip-flop so both conditionals check for :rad:


hoverboards.each do |hoverboard|
  if (hoverboard == :rad)..(hoverboard == :rad)
    hoverboards_to_buy_maybe << hoverboard
  end
end

When we make our third iteration and hoverboard is :rad we will meet the first condition and set the flip-flop state to true. We’ll add the :rad hoverboard to hoverboards_to_buy_maybe because the state is now set to true, and afterwards we’ll check the second condition. The second condition also evaluates to true, so we change the flip-flop state back to false and we move to our next iteration. Since we never find :rad again the flip-flop state never returns to true and our final boards_to_buy_maybe looks like this:

[:rad]

We met the first condition, executed the block and met the second condition in a single iteration.

The three-dot version of this same flip-flop behaves quite differently, evaluating only one of the conditionals for each iteration:


(hoverboard == :rad)...(hoverboard == :ugly)

The flip-flop starts out set to false, so until we find :rad it will remain false. We only check the first condition with the three-dot until we find the :rad hoverboard and the flip-flop state changes to true. If we don’t meet the first condition we never evaluate the second condition.

Once we’ve met the first condition our flip-flop is true and we will stop evaluating the first condition until the second is met. So on each iteration we now check only for the :ugly hoverboard.

When we find the :ugly hoverboard the second condition evaluates to true and the flip-flop is set back to false. We stop evaluating the second condition and continue through our loop evaluating only the first condition, which in our case never evaluates to true again.

The three-dot change in behavior is especially obvious when both conditions are the same:


(hoverboard == :rad)...(hoverboard == :rad)
# hoverboards_to_buy_maybe: [:rad]

When we find :rad and the first condition evaluates to true, we execute the body of our if block, adding :rad to boards_to_buy_maybe. Unlike the two-dot version we do not evaluate the second condition on this iteration, so our flip-flop state remains true.

On our next iteration hoverboard is now equal to :lasers.


(:lasers == :rad)...(:lasers == :rad)
# hoverboards_to_buy_maybe: [:rad, :lasers]

We don’t evaluate the first condition at all since the flip-flop state is true. Instead we check the second condition, which evaluates to false, and we go on our merry way. Since we don’t have another :rad in our hoverboards we will never meet the second condition, and the flip-flop stays true until the end of the loop.

We end up with this in our final hoverboards_to_buy_maybe:


[:rad, :lasers, :peuce, :ugly, :double_decker]

We found :rad and flipped to true but didn’t check the second condition, because three-dot flip-flops check only one condition on each iteration. By the time we came back again it was too late to find a :rad hoverboard so we never tripped our second condition and the flip-flop remained true all the way down.

Flipped and flopped

To review, the two-dot version of the flip-flop operator evaluates both conditions on each iteration. The three-dot version will evaluate only a single condition on each pass: the first condition if the flip-flop state is true and the second condition if the flip-flop state is false. You’ll need to decide which flip-flop is the right one for your particular hoverboard shopping use case.

I hope that this “sometimes I care” hoverboard example has made clear the value of Ruby’s flip-flop operator, because I have no idea why anyone would actually want to use this thing in the real world.

I assume that someone made up the flip-flop operator for a reason, so if you have a good application for flip-flop operators please leave a comment in the New Relic community forum. I am incredibly curious.


if (true)..(false)
  puts “<3 Jonan"
end

 

Ruby and Hoverboard image courtesy of Shutterstock.com.

Jonan spends most of his time staring into tiny boxes and pushing buttons. He likes Ruby, Go, machine learning and playing with robots. View posts by .

Interested in writing for New Relic Blog? Send us a pitch!