Чому в Ruby об'єкт Regexp вважається "хибним"?


16

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

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

Однак правдивість і хибність не обмежуються примірниками цих двох класів, концепція є універсальною і застосовується до кожного об'єкта в Ruby. Кожен об'єкт є або truthy або falsy . Правила дуже прості. Зокрема, лише два об'єкти є хибними :

  • nil, одиничний екземпляр NilClassі
  • false, одиночний екземпляр FalseClass

Кожен інший об'єкт є правдою . Сюди входять навіть об'єкти, які вважаються хибними в інших мовах програмування, наприклад

Ці правила вбудовані в мову і не визначаються користувачем. Немає to_boolнеявного перетворення чи нічого подібного.

Ось цитата зі специфікації мови ISO Ruby :

6.6 Булеві значення

Об'єкт класифікується або в істинний об'єкт, або в хибний об'єкт .

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

Об'єкти, крім БРЕХНІ і нуля підрозділяється на trueish об'єкти. true - єдиний екземпляр класу TrueClass(див. 15.2.5), якому оцінюється істинний вираз (див. 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 . Усі три реалізації реально погоджуються між собою та не згодні зі специфікацією ISO Ruby Language та моїм тлумаченням 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

Це не помилка. Те, що відбувається - Рубі переписує код так, що

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літералом, який використовує інтерполяцію, наприклад /#{''}/.
Йорг W Міттаг

@ JörgWMittag Я думаю, ти маєш рацію. Розглядаючи компілятор і генерований байт-код, схоже, що у випадку динамічного повторного розбору дерево розбору переписується, щоб явно додати $_як вузол, який компілятор обробляє як звичайний, тоді як у статичному випадку все це вирішується компілятор. Мені соромно, бо "ей, ти можеш побачити, де тут переписано дерево розбору" - це гарна відповідь.
мат

4

Це результат (наскільки я можу сказати) недокументованої функції рубінової мови, що найкраще пояснюється цією специфікацією :

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"

Щоб зробити речі ще більш заплутаними, $_(разом із $-) не є глобальною змінною; вона має локальну сферу застосування .


Коли починається рубіновий скрипт $_ == nil.

Отже, код:

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

Інтерпретується як:

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

... Що повертає фальси.

З іншого боку, для нелітерального зворотного перетворення (наприклад, r = //або Regexp.new('')) ця спеціальна інтерпретація не застосовується.

//є правдою; так само, як і всі інші об'єкти в рубіні, крім 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і виконувати перевірку регулярних виразів щодо цього значення явно.

Мені невідомий жоден випадок використання для проведення цієї перевірки з порожнім регулярним виразом, особливо коли він визначається як буквальне значення. Результат, який ви підкреслили, дійсно захопив би більшість розробників рубінів поза захистом.


Я використовував лише умовне як приклад. !// #=> trueмає однакову поведінку і не перебуває в умовному стані. Я не міг знайти жодного булевого контексту (умовного чи ні), де він веде себе так, як очікувалося.
Йорг W Міттаг

@ JörgWMittag Ви маєте на увазі, наприклад, !// ? true : falseповернення true? Я думаю, що це знову той самий момент - це трактується так:!(// =~ nil) ? true : false
Том Лорд,

Якщо ви встановили вручну $_ = 'hello world'перед запуском вищевказаного коду, ви повинні отримати інший результат - тому що // =~ 'hello world', але не відповідає nil.
Том Лорд

Ні, я маю на увазі !// без умовних оцінок до true. Спеціалізація, яку ви цитували, стосується Regexpбуквального в умовному, але в цьому прикладі немає умовного, тому ця специфікація не застосовується.
Йорг W Міттаг

2
Ага ... Так, дуже дивно. Здається, що поведінка пов'язана: puts !//; $_ = ''; puts !//- Я вважаю, що аналізатор розширює її як макрос; це не обов'язково бути всередині умовного?
Том Лорд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.