Почему диапазон с неверными аргументами иногда не вызывает ошибку аргумента?

Следующий код вызывает ошибку аргумента:

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

что я думаю, потому что он оценивает:

false..true

и в диапазоне используются разные типы классов: TrueClass и FalseClass. Однако следующий код не вызывает ошибку. Это почему? Enumerable#collect ловит?

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

Добавлено позже: если fcn возвращает 15, то оценивается только первая половина диапазона

def fcn(x)
  puts x
  15
end

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

но если мы изменим возвращаемое значение на 16, тогда ввод будет

# => 1
# => 2

Странно, потому что в этом случае выражение оценивается как

true..false

И такой диапазон недействителен в соответствии с ответом sawa ниже.

Тогда в первом случае (с возвращаемым значением def 15) у нас есть только частичный диапазон без конечной части? Это так странно :)


person yaru    schedule 12.09.2012    source источник
comment
Довольно интересно. Кажется (выражение1)..(выражение2) ? a : b только вычислить выражение1, чтобы определить его значение. Вы можете оценить только someRange ? a : b, чтобы проверить это. Похоже, это не имеет ничего общего с Enumeralbe#collect.   -  person halfelf    schedule 12.09.2012
comment
Вопрос можно упростить: почему (true..false); 1 работает, а x = (true..false); 1 нет? кстати, этого не происходит с Ruby 1.8.7.   -  person tokland    schedule 12.09.2012
comment
halfelf, я переписываю код без использования условного оператора, но просто если/тогда и по-прежнему без исключения (11..20).collect do |i| if (i % 4 == 0)..(i % 3 == 0) then i else nil end end   -  person yaru    schedule 12.09.2012


Ответы (2)


В Ruby if start..finish — это триггер, специальный синтаксис для написания быстрых и малопонятных скриптов. Обычно используется в циклах:

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

Когда первое условие истинно, все условие считается истинным при каждом последующем выполнении, пока второе условие не будет оценено как истинное. Вы можете поиграть с приведенным выше сценарием, чтобы получить представление. Вот мой ввод и вывод:

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

Обратите внимание, что если условие не запущено, Ruby не оценивает условие завершения, потому что это бесполезно.

Хотя нет особого смысла использовать это вне циклов, Ruby не ограничивает это.

>> if nil..raise; :nothing_gonna_happen; end
=> nil
person Simon Perepelitsa    schedule 12.09.2012

Прежде всего, обратите внимание на следующие допустимые и недопустимые литералы:

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

Таким образом, вопрос сводится к тому, почему выражения, которые оцениваются как false..true и true..false, становятся допустимыми, если они встроены в условие цикла. Согласно документации, литерал диапазона в состоянии цикла фактически не создает диапазон. Это скорее имеет особое значение, родственное sed и awk. То есть, истинность начала диапазона инициирует цикл, а истинность конца диапазона завершает его. Некоторые примеры можно найти здесь.

person sawa    schedule 12.09.2012
comment
это не связано с условием цикла, см. мой комментарий к вопросу. - person tokland; 12.09.2012