On Ranges as conditions

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?

On Sat, Oct 3, 2009 at 1:15 PM, Rafael Cunha de Almeida
[email protected] 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/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

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

On Sat, Oct 3, 2009 at 12:15 PM, Rafael Cunha de Almeida
[email protected]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

On Saturday, October 3, 2009, Markus R. [email protected] 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.

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

On Wed, Oct 7, 2009 at 1:08 PM, Ken B. [email protected] 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]