This is part 3 of a series on Weird Ruby. Don’t miss Weird Ruby Part 1: The Beginning of the End, Weird 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.
Have 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.
The views expressed on this blog are those of the author and do not necessarily reflect the views of New Relic. Any solutions offered by the author are environment-specific and not part of the commercial solutions or support offered by New Relic. Please join us exclusively at the Explorers Hub (discuss.newrelic.com) for questions and support related to this blog post. This blog may contain links to content on third-party sites. By providing such links, New Relic does not adopt, guarantee, approve or endorse the information, views or products available on such sites.