Почему объект Regexp в Ruby считается «ложным»?

16

У Руби есть универсальное представление о « правдивости » и « ложности ».

Рубин делает два конкретных классов для объектов Boolean, TrueClassи FalseClass, с одноплодными случаями , обозначенных специальными переменными trueи false, соответственно.

Однако правдивость и ложность не ограничиваются экземплярами этих двух классов, концепция универсальна и применима к каждому объекту в Ruby. Каждый объект является либо правдивым, либо ложным . Правила очень просты. В частности, только два объекта являются ложными :

  • nil, единичный случай NilClassи
  • falseединичный случай FalseClass

Каждый другой объект является truthy . Это включает даже объекты, которые считаются ложными в других языках программирования, таких как

Эти правила встроены в язык и не определяются пользователем. Не существует to_boolнеявного преобразования или чего-либо подобного.

Вот цитата из спецификации языка Ruby ISO :

6.6 Булевы значения

Объект классифицируется как истинный объект или ложный объект .

Только ложь и ноль являются ложными объектами. false - единственный экземпляр класса FalseClass(см. 15.2.6), для которого оценивается ложное выражение (см. 11.5.4.8.3). nil - единственный экземпляр класса NilClass(см. 15.2.4), для которого оценивается nil-выражение (см. 11.5.4.8.2).

Объекты, отличные от false и nil , классифицируются как истинные объекты. Значение true является единственным экземпляром класса TrueClass(см. 15.2.5), для которого оценивается выражение true (см. 11.5.4.8.3).

Исполняемый Ruby / Spec, похоже, согласен :

it "considers a non-nil and non-boolean object in expression result as true" do
  if mock('x')
    123
  else
    456
  end.should == 123
end

Согласно этим двум источникам, я бы предположил, что Regexps также правдивы , но, согласно моим тестам, они не являются:

if // then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are falsy'

Я проверял это на YARV 2.7.0-preview1 , TruffleRuby 19.2.0.1 и JRuby 9.2.8.0 . Все три реализации согласуются друг с другом и не соответствуют спецификации языка Ruby ISO и моей интерпретации Ruby / Spec.

Точнее говоря, Regexpобъекты, которые являются результатом вычисления Regexp литералов, являются ложными , тогда как Regexpобъекты, которые являются результатом некоторых других выражений, являются правдивыми :

r = //
if r then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are truthy'

Это ошибка или желаемое поведение?

Йорг Миттаг
источник
Интересно то, что Regex.new("a")это правда.
mrzasa
!!//ложно, но !!/r/верно. Странно действительно.
максимум
@max !!/r/производит falseдля меня использование (RVM) Ruby 2.4.1.
3limin4t0r
Извините, мой плохой @ 3limin4t0r. Вы правы. Должно быть, я сделал что-то действительно глупое, например, пропустив восклицательный знак.
максимум
2
Гипотеза, я думаю, что //в if // thenинтерпретируется как тест (сокращение для if //=~nil then) (это всегда ложно, независимо от шаблона), а не как экземпляр Regexp.
Казимир и Ипполит

Ответы:

6

Это не ошибка Происходит то, что Ruby переписывает код так, чтобы

if /foo/
  whatever
end

эффективно становится

if /foo/ =~ $_
  whatever
end

Если вы запускаете этот код в обычном скрипте (и не используете -eопцию), вы должны увидеть предупреждение:

warning: regex literal in condition

Это, вероятно, несколько сбивает с толку большую часть времени, поэтому и дается предупреждение, но может быть полезно для одной строки, используя -eопцию. Например, вы можете напечатать все строки, соответствующие заданному регулярному выражению, из файла с

$ ruby -ne 'print if /foo/' filename

( По умолчанию аргумент для printэто $_так.)

матовый
источник
Смотрите также -n, -p, -aи -lопционы, а также несколько методов ядра, которые доступны только тогда , когда -nили -pиспользуются ( chomp, chop, gsubи sub).
матовый
Также есть вторая часть парсера, где выдается это предупреждение. Я не знаю, что там происходит, хотя.
матовый
Я считаю, что «вторая часть» - это та, которая действительно относится к этому вопросу. NODE_LITс типом T_REGEXP. Тот, который вы разместили в своем ответе, предназначен для динамического Regexpлитерала , то есть Regexpлитерала, который использует интерполяцию, например /#{''}/.
Йорг Миттаг
@ JörgWMittag Я думаю, ты прав. Пошаривая в компиляторе и сгенерированном байт-коде, похоже, что в случае динамического регулярного выражения дерево разбора переписывается для явного добавления $_в качестве узла, который компилятор обрабатывает как обычно, в то время как в статическом случае все это обрабатывается компилятор. Это позор для меня, потому что «эй, вы можете видеть, где дерево синтаксического анализа переписано здесь» делает хороший ответ.
матовый
4

Это результат (насколько я могу судить) недокументированной возможности языка ruby, которая лучше всего объясняется этой спецификацией :

it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
  -> {
    eval <<-EOR
    $_ = nil
    (true if /foo/).should_not == true
    $_ = "foo"
    (true if /foo/).should == true
    EOR
  }.should complain(/regex literal in condition/)
end

Вы можете вообще думать $_как "последняя строка, прочитанная gets"

Чтобы сделать вещи еще более запутанными, $_(наряду с $-) не является глобальной переменной; это имеет локальную сферу применения .


Когда запускается скрипт ruby $_ == nil.

Итак, код:

// ? 'Regexps are truthy' : 'Regexps are falsey'

Интерпретируется как:

(// =~ nil) ? 'Regexps are truthy' : 'Regexps are falsey'

... который возвращает фальси.

С другой стороны, для не буквального регулярного выражения (например, r = //или Regexp.new('')) это специальное толкование не применяется.

//правда; как и все остальные объекты в ruby, кроме nilи false.


Если сценарий ruby ​​не запущен непосредственно в командной строке (то есть с -eфлагом), анализатор ruby ​​будет отображать предупреждение против такого использования:

предупреждение: регулярное выражение в состоянии

Вы можете использовать это поведение в скрипте, например:

puts "Do you want to play again?"
gets
# (user enters e.g. 'Yes' or 'No')
/y/i ? play_again : back_to_menu

... Но было бы более нормально назначить локальную переменную результату getsи выполнить проверку регулярного выражения по этому значению явно.

Я не знаю ни одного варианта использования для выполнения этой проверки с пустым регулярным выражением, особенно когда оно определено как буквальное значение. Выделенный вами результат действительно застал бы врасплох большинство разработчиков ruby.

Том Лорд
источник
Я использовал только условные в качестве примера. !// #=> trueимеет такое же поведение и не является условным. Я не смог найти никакого логического контекста (условного или нет), где он ведет себя как ожидалось.
Йорг Миттаг
@ JörgWMittag Вы имеете в виду, например, !// ? true : falseвозврат true? Я думаю, что это то же самое снова - это интерпретируется как:!(// =~ nil) ? true : false
Том Лорд
Если вы установили вручную $_ = 'hello world'перед запуском вышеуказанного кода, то вы должны получить другой результат - потому что // =~ 'hello world', но не совпадает nil.
Том Лорд
Нет, я имею ввиду !// без условных оценок true. Указанная вами спецификация относится к Regexpлитералу в условном выражении, но в этом примере условного обозначения не существует, поэтому данная спецификация не применяется.
Йорг Миттаг
2
Ах .. Да, очень удивительно. Поведение кажется связанным, хотя: puts !//; $_ = ''; puts !//- я полагаю, потому что синтаксический анализатор расширяет его как макрос; это не обязательно должно быть внутри условного?
Том Лорд