Why does a range with invalid arguments sometimes not cause an argument error?
Asked Answered
B

2

9

The following code causes an argument error:

n = 15
(n % 4 == 0)..(n % 3 == 0)
# => bad value for range (ArgumentError)

which I think is because it evaluates to:

false..true

and different types of classes are used in range: TrueClass and FalseClass. However, the following code does not raise an error. Why is that? Does Enumerable#collect catch it?

(11..20).collect { |i| (i % 4 == 0)..(i % 3 == 0) ? i : nil }
# => no error

Added later: If fcn returns 15, then only first half of range is evaluated

def fcn(x)
  puts x
  15
end

if  (fcn(1) % 4 == 0)..(fcn(2) % 3 == 0); end
# => 1

but if we change return value to 16 then input will be

# => 1
# => 2

It's strange, because in this case expression evaluates to

true..false

And such kind of range is invalid according to sawa's answer below.

Then in first case (with def's return value 15) we have only partial range with no ending part? It's so strange :)

Blocked answered 12/9, 2012 at 10:29 Comment(3)
Quite interesting. It seems "(expression1)..(expression2) ? a : b" only calculate expression1 to determine its value. You can only "eval someRange ? a : b" to test this. Looks like nothing to do with Enumeralbe#collect.Engross
The question can be simplified: why (true..false); 1 works but x = (true..false); 1 does not? btw, this does not happen with Ruby 1.8.7.Applewhite
halfelf, I rewrite the code without using conditional operator, but simple if/then and still no exception (11..20).collect do |i| if (i % 4 == 0)..(i % 3 == 0) then i else nil end endBlocked
A
8

In Ruby if start..finish is a flip-flop, a special syntax for writing fast and obscure scripts. It is usually used in loops:

while input = gets
  puts "Processing #{input.inspect}" if input =~ /start/ .. input =~ /end/
end

When the first condition is true, the whole condition is considered true on every consecutive execution until the second condition evaluates to true. You can play with the above script to get the idea. Here is my input & output:

foo
start
Processing "start\n"
foo
Processing "foo\n"
bar
Processing "bar\n"
end
Processing "end\n"
foo
bar
start
Processing "start\n"

Note that if the condition is not started Ruby doesn't evaluate the finishing condition because this is useless to do so.

Although it does not make much sense to use this outside of loops, Ruby is not restricting that.

>> if nil..raise; :nothing_gonna_happen; end
=> nil
Altagraciaaltaic answered 12/9, 2012 at 13:9 Comment(0)
P
0

First of all, note the following valid and invalid literals:

true..true # => valid
false..false # => valid
true..false # => invalid
false..true # => invalid

So the question reduces to why the expressions that evaluate to false..true and true..false become valid when embedded in a loop condition. According to documentation, a range literal in loop condition does not actually create a range. It rather has a special meaning akin to sed and awk. That is, the truth of the start of a range initiates the loop and truth of the end of the range terminates it. Some examples found here.

Prorogue answered 12/9, 2012 at 11:48 Comment(1)
it's not related to being a loop condition, see my comment to the question.Applewhite

© 2022 - 2024 — McMap. All rights reserved.