Почему объект Regexp в Ruby считается "ложным"?
У Руби есть универсальное представление о "правдивости" и "ложности".
В Ruby есть два специальных класса для булевых объектов: TrueClass
и FalseClass
, причем одноэлементные экземпляры обозначаются специальными переменными true
и false
соответственно.
Однако правдивость и ложность не ограничиваются экземплярами этих двух классов, концепция универсальна и применима к каждому объекту в Ruby. Каждый объект является либо правдивым, либо ложным. Правила очень просты. В частности, только два объекта являются ложными:
Каждый другой объект правдив. Это включает даже объекты, которые считаются ложными в других языках программирования, таких как
Эти правила встроены в язык и не определяются пользователем. to_bool
не существует неявного преобразования или чего-либо подобного.
Вот цитата из спецификации языка Ruby ISO:
6.6 Булевы значения
Объект классифицируется как истинный объект или ложный объект.
Только false и nil являются ложными объектами. 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
Согласно этим двум источникам, я бы предположил, что Regexp
также правдивы, но, согласно моим тестам, это не так:
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'
Это ошибка или желаемое поведение?
Ответы
Ответ 1
Это не ошибка. Происходит следующее: Ruby переписывает код так, чтобы
if /foo/
whatever
end
фактически становится
if /foo/ =~ $_
whatever
end
Если вы запускаете этот код в обычном сценарии (и не используете опцию -e
), вы должны увидеть предупреждение:
warning: regex literal in condition
В большинстве случаев это, вероятно, несколько сбивает с толку, поэтому и дается предупреждение, но может быть полезно для одной строки, используя опцию -e
. Например, вы можете напечатать все строки, соответствующие заданному регулярному выражению, из файла с
$ ruby -ne 'print if /foo/' filename
(Значением по умолчанию для print
также является $_
.)
Ответ 2
Это результат (насколько я могу судить) недокументированной особенности языка рубина, которая лучше всего объясняется этой спецификацией:
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
"
Чтобы сделать ситуацию еще более запутанной, $_
(вместе с $-
) не является not глобальной переменной; он имеет локальный охват.
Когда запускается скрипт 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.