# On Ranges as conditions

**URL:** <https://www.ruby-forum.com/t/on-ranges-as-conditions/176641>\
**Category:** Ruby\
**Created:** [October 3, 2009, 7:15pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641 "2009-10-03T19:15:49Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/r/b9e5f3/32.png) [@Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/u/Rafael_Cunha_de_Alm)\
**Post date:** [October 3, 2009, 7:15pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641/1 "2009-10-03T19:15:49Z")

</div>

Reading a few tutorials I found this piece of code:

while input = gets  
input = input.chomp  
puts input + " triggered" if input =~ /start/ … input =~ /end/  
end

I understand what it does. If I type start, end or anything in between  
it prints  
“triggered” after the input. Now, if I’m out of the start … end block,  
the if  
fails.

What I don’t understand is how it does it. For instance, if I use the  
interpreter to do something like this:

irb(main):038:0\> input = ‘fooo’  
=\> “fooo”  
irb(main):039:0\> puts input + " triggered" if input =~ /start/ …  
input  
=~ /end/  
=\> nil  
irb(main):040:0\> input = ‘start’  
=\> “start”  
irb(main):041:0\> puts input + " triggered" if input =~ /start/ …  
input  
=~ /end/  
start triggered  
=\> nil  
irb(main):042:0\> input = ‘bar’  
=\> “bar”  
irb(main):043:0\> puts input + " triggered" if input =~ /start/ …  
input  
=~ /end/  
=\> nil

it doesn’t work as I’d expect. I thought the if clause there would  
somehow set a  
global to say “the first regex was matched” or something like that. But  
it  
doesn’t seem to be the way it works. It only seems to work inside of a  
while  
loop. I thought it might have something to do with the $\_ global. But I  
made a  
test in the interpreter which ruled out that theory:

irb(main):045:0\> $\_ = ‘foo’  
=\> “foo”  
irb(main):046:0\> input = ‘foo’  
=\> “foo”  
irb(main):047:0\> puts input + " triggered" if input =~ /start/ …  
input  
=~ /end/  
=\> nil  
irb(main):048:0\> $\_ = input = ‘start’  
=\> “start”  
irb(main):049:0\> puts input + " triggered" if input =~ /start/ …  
input  
=~ /end/  
start triggered  
=\> nil  
irb(main):050:0\> $\_ = input = ‘foo’  
=\> “foo”  
irb(main):051:0\> puts input + " triggered" if input =~ /start/ …  
input  
=~ /end/  
=\> nil  
irb(main):052:0\>

Can anyone help me find out what’s going on?

---

<div class="post-metadata">

**Author:** ![Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/r/b9e5f3/32.png) [@Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/u/Rafael_Cunha_de_Alm)\
**Post date:** [October 3, 2009, 9:07pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641/2 "2009-10-03T21:07:36Z")

</div>

On Sat, Oct 3, 2009 at 1:15 PM, Rafael Cunha de Almeida  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> the if  
> irb(main):040:0\> input = ‘start’
> 
> ```
> irb(main):046:0> input = 'foo'
> irb(main):050:0> $_ = input = 'foo'
> => "foo"
> irb(main):051:0> puts input + " triggered" if input =~ /start/ .. input
> 
> ```
> 
> =~ /end/  
> =\> nil  
> irb(main):052:0\>
> 
> Can anyone help me find out what’s going on?

As far as I can tell, from reading the 1.8 parse.c code, the problem  
is that conditional ranges really aren’t Range instances but are  
really a kind of syntactic sugar. Every time you retype the source  
line into irb, you get an entirely new parse, and a new conditional  
range in the initial state.

–  
Rick DeNatale

Blog: [http://talklikeaduck.denhaven2.com/](http://talklikeaduck.denhaven2.com/)  
Twitter: [http://twitter.com/RickDeNatale](http://twitter.com/RickDeNatale)  
WWR: [http://www.workingwithrails.com/person/9021-rick-denatale](http://www.workingwithrails.com/person/9021-rick-denatale)  
LinkedIn: [http://www.linkedin.com/in/rickdenatale](http://www.linkedin.com/in/rickdenatale)

---

<div class="post-metadata">

**Author:** ![Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/r/b9e5f3/32.png) [@Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/u/Rafael_Cunha_de_Alm)\
**Post date:** [October 3, 2009, 9:13pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641/3 "2009-10-03T21:13:28Z")

</div>

It isn’t a range, it’s a “flip-flop” which happens to use the same  
operator (’…’) as range construction.

While useful in some sed/awk like scripts it’s not a widely used  
feature.

– MarkusQ

---

<div class="post-metadata">

**Author:** ![Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/r/b9e5f3/32.png) [@Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/u/Rafael_Cunha_de_Alm)\
**Post date:** [October 3, 2009, 11:18pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641/4 "2009-10-03T23:18:09Z")

</div>

On Sat, Oct 3, 2009 at 12:15 PM, Rafael Cunha de Almeida  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)wrote:

> the if  
> =\> nil  
> input  
> test in the interpreter which ruled out that theory:  
> =\> “start”  
> =\> nil  
> irb(main):052:0\>
> 
> Can anyone help me find out what’s going on?

# (this entire response can be directly placed into a script – not the

above quote, though)

# 

# The first condition turns it on (causes it to evaluate to true)

# the second turns it off (causes it to evaluate back to false).

# In this example, you would get the output

# 

# start triggered

# instruction2 triggered

# instruction3 triggered

# end triggered

# start triggered

# instruction6 triggered

# instruction7 triggered

# instruction8 triggered

# end triggered

# 

# Give it a try

$stdin = DATA

while input = gets  
input = input.chomp  
puts input + " triggered" if input =~ /start/ … input =~ /end/  
end

**END**  
instruction0  
instruction1  
start  
instruction2  
instruction3  
end  
instruction4  
instruction5  
start  
instruction6  
instruction7  
instruction8  
end  
instruction9

---

<div class="post-metadata">

**Author:** ![Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/r/b9e5f3/32.png) [@Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/u/Rafael_Cunha_de_Alm)\
**Post date:** [October 4, 2009, 3:27pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641/5 "2009-10-04T15:27:58Z")

</div>

On Saturday, October 3, 2009, Markus R. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> It isn’t a range, it’s a “flip-flop” which happens to use the same operator (‘…’) as range construction.
> 
> While useful in some sed/awk like scripts it’s not a widely used feature.

Well, it is used as often as you find the problem it solves, no matter  
the language.

---

<div class="post-metadata">

**Author:** ![Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/r/b9e5f3/32.png) [@Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/u/Rafael_Cunha_de_Alm)\
**Post date:** [October 7, 2009, 2:09pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641/6 "2009-10-07T14:09:19Z")

</div>

On Sun, 04 Oct 2009 04:06:56 +0900, Rick DeNatale wrote:

> > I understand what it does. If I type start, end or anything in between  
> > Â Â Â Â irb(main):039:0\> puts input + " triggered" if input =~  
> > Â Â Â Â irb(main):042:0\> input = ‘bar’  
> > a while loop. I thought it might have something to do with the $\_  
> > Â Â Â Â =\> nil  
> > =~ /end/  
> > Â Â Â Â =\> nil  
> > Â Â Â Â irb(main):052:0\>
> > 
> > Can anyone help me find out what’s going on?
> 
> As far as I can tell, from reading the 1.8 parse.c code, the problem is  
> that conditional ranges really aren’t Range instances but are really a  
> kind of syntactic sugar. Every time you retype the source line into  
> irb, you get an entirely new parse, and a new conditional range in the  
> initial state.

Pretty cool! Thanks for pointing this syntax out! I don’t recall seeing  
it in Pickaxe.

One question:

irb(main):013:0\> a=%w{foo start bar end baz start foo bar end baz}  
=\> [“foo”, “start”, “bar”, “end”, “baz”, “start”, “foo”, “bar”, “end”,  
“baz”]  
irb(main):014:0\> a.each {|i| puts i + (if i =~ /start/ … i =~ /end/  
then  
" triggered" else “” end) }  
foo  
start triggered  
bar triggered  
end triggered  
baz  
start triggered  
foo triggered  
bar triggered  
end triggered  
baz  
=\> [“foo”, “start”, “bar”, “end”, “baz”, “start”, “foo”, “bar”, “end”,  
“baz”]  
irb(main):015:0\> a.each {|i| puts i + (if i =~ /start/ … i =~ /end/  
then " triggered" else “” end) }  
foo  
start triggered  
bar triggered  
end triggered  
baz  
start triggered  
foo triggered  
bar triggered  
end triggered  
baz

Shouldn’t the last version (using … instead of …) logically omit the  
end line from the triggered section?

–Ken

---

<div class="post-metadata">

**Author:** ![Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/r/b9e5f3/32.png) [@Rafael\_Cunha\_de\_Alm](https://www.ruby-forum.com/u/Rafael_Cunha_de_Alm)\
**Post date:** [October 8, 2009, 5:22pm UTC](https://www.ruby-forum.com/t/on-ranges-as-conditions/176641/7 "2009-10-08T17:22:05Z")

</div>

On Wed, Oct 7, 2009 at 1:08 PM, Ken B. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> Pretty cool! Thanks for pointing this syntax out! I don’t recall seeing  
> it in Pickaxe.  
> It’s definitely in the first (2001) edition as “Ranges as conditions”:  
> \_A range used in a boolean expression acts as a flip-flop.  
> \_ It has two states, set and unset, and is initially unset.  
> \_ On each call, the range cycles through the state machine …  
> \_ The range returns true if it is in the set state at the end of the  
> call,  
> \_ and false otherwise.  
> \_The two-dot form of a range behaves slightly differently than the  
> three-dot form.  
> \_ When the two-dot form first makes the transition from unset to set,  
> \_it immediately evaluates the end condition and makes the transition  
> accordingly.  
> \_This means that if expr1 and expr2 both evaluate to true on the same  
> call,  
> \_the two-dot form will finish the call in the unset state.  
> \_However, it still returns true for this call.  
> .  
> _But_ - as Rick DeNatale and Markus R. posted - in these  
> expressions  
> the “…” and “…” don’t create ranges but create “flip-flop”  
> expressions,  
> so I think it’s misleading to call these “ranges”,  
> and I think the explanations in “The Ruby P.ming Language” are  
> better  
> because they make that clear.  
> bar triggered  
> then " triggered" else “” end) }
> 
> Shouldn’t the last version (using … instead of …) logically omit the  
> end line from the triggered section?

If I understand your question correctly, according to “Programming Ruby”  
and “The Ruby P.ming Language” the “…” and “…” refer to  
_when_ the flip-flop expressions are or are not evaluated,  
as the quote above from Pickaxe (“Programming Ruby”) says.  
Does the following (irb) code explain this?

An = [1, 2, 3, 5, 2.0, 3.0, 5.0] #=\> [1, 2, 3, 5, 2.0, 3.0, 5.0]  
def ffn(b, e, dots = “…”)  
aa = []  
ffop = “x==#{b} #{dots} x\>=#{e}”  
expr = “An.each { |x| aa \<\< x if #{ffop} }”  
puts "expr #=\> " + expr  
eval expr  
aa  
end  
#=\> nil  
ffn 2, 3, “…”

# expr #=\> An.each { |x| aa \<\< x if x==2 … x\>=3 }

#=\> [2, 3, 2.0, 3.0]  
ffn 2, 3, “…”

# expr #=\> An.each { |x| aa \<\< x if x==2 … x\>=3 }

#=\> [2, 3, 2.0, 3.0]  
fn 2, 2, “…”

# expr #=\> An.each { |x| aa \<\< x if x==2 … x\>=2 }

#=\> [2, 2.0]  
ffn 2, 2, “…”

# expr #=\> An.each { |x| aa \<\< x if x==2 … x\>=2 }

#=\> [2, 3, 2.0, 3.0]
