Technology Programming

The Curious Case of the Flip-Flop

When the Range operator is used in a conditional statement, it does something totally unexpected: it doesn't create a Range object. Instead, it acts as a "flip-flop" operator, a feature carried over from archaic text processing languages such as awk, sed and Perl. Though it is a Range analog, of sorts.

But first, a warning. The flip-flop operator is a hotly contested feature of Ruby. It's still struggling to find an idiomatic use case, except for a few very rarely needed things.

It's not something you'll likely reach for on a daily, weekly or even monthly basis. The only thing you really need to know about it is what it does, and that's only in case you encounter it in someone else's code. Many even go as far to say not to use the flip-flop operator, that it only adds confusion. The choice is yours though.

These types of flip-flop expressions look at bit different. First, they're usually in the conditional portion of an if or while statement. Second, the left and right of the Range expression itself are usually boolean expressions. For, for example, it might look something like this.

if a==3 .. a==5# Do somethingend

The flip-flop expression will evaluate to false until the first (left hand) expression is true. The flip-flop expression will then evaluate to true until the right-hand expression evaluates to true. It will then continue to evaluate to false until the left-hand expression evaluates to true again, and then it will flip the other way. It flips and flops between the true and false state, hence the name "flip-flop."

The flip-flop expression can also be exclusive. With the typical double dot version, when the second expression evaluates to false, the flip-flop expression will evaluate to true that one last time. When using the three dot notation, the flip-flop expression will evaluate to false as soon as the second expression evaluates to false.

So how is this a "range analog" as was mentioned before? In the example above, assuming a is a range of 1 to 10, the flip-flop will be true from the values of a from 3 to 5 (including 5). In addition, if there are any additional 3's in the sequence, it will also be true for any future sequence of 3 to 5.


Describing how to use the flip-flop operator can be tough. Firstly, it's not used very often. In fact, many say not to use it at all. It's archaic, not expressive and can easily lead to confusion. Though it's there in your toolbox, and should you find a need to use it, there's no strong reason not to use it.

It's most often seen used with regular expressions. Say you want to print all lines from a text file starting with the line that begins with BEGIN and ends with the line that begins with END. You could simply iterate over all the lines of the file, use a single flip-flop in an if statement and puts the lines.

First, the data file.

These lines won't be printed.They're here for debugging purposes onlyand as notes for other programmers.BEGINWelcome to AwesomeProgram 3.1415Press X to begin!ENDAnd these lines won't be printed either.

And the code. Note that the .. and … operators bind very loosely, so there's no need for parentheses.

#!/usr/bin/env rubyFile.readlines(ARGV[0]).each do|l|if l =~ /^BEGIN/ .. l =~ /^END/puts lendend

You could also use it to extract ranges of objects from sequences. For example, you have the range of numbers from 1 to 100 and you way to select only the numbers between a multiple of 5 and the next highest multiple of 8. So from 5 to 8, then from 10 to 16, then 20 to 24, etc. Your first try might be something like this (but it would be wrong).

(1..100).select{|i| i%5==0 .. i%8==0 }

This looks reasonable, so why is it wrong? Remember that the flip-flop operator can only be used inside of conditional statements. Here, the selectblock is merely acting on the truthiness of the result of the block passed to it, there is no conditional statement here. The conditional statement must exist to trigger the creation of the flip-flop, it's a special behind the scenes thing that has to happen just right. So you'll have to add a bit of superfluous code to satisfy this need.

(1..100).select{|i| true if i%5==0 .. i%8==0 }

That looks a bit silly, "true if this other thing is true," but it works. Also, the flip-flop can be used wherever a conditional expression is expected, not only in if statements. This includes loops and the conditional operator.

Leave a reply