Dlaczego obiekt Regexp jest uważany za „fałsz” w Rubim?

16

Ruby ma uniwersalną ideę „ prawdy ” i „ fałszu ”.

Ruby ma mieć dwie klasy specyficzne dla Boolean obiektów, TrueClassi FalseClass, z pojedynczych przypadkach oznaczonych zmiennych specjalnych truei false, odpowiednio.

Jednak prawdomówność i fałsz nie ograniczają się do przypadków tych dwóch klas, koncepcja jest uniwersalna i dotyczy każdego obiektu w Rubim. Każdy przedmiot jest prawdomówny lub fałszywy . Zasady są bardzo proste. W szczególności tylko dwa obiekty są fałszywe :

Każdy inny przedmiot jest prawdziwy . Obejmuje to nawet obiekty, które są uważane za fałsz w innych językach programowania, takich jak

Reguły te są wbudowane w język i nie są definiowane przez użytkownika. Nie ma to_boolniejawnej konwersji lub czegoś podobnego.

Oto cytat ze specyfikacji języka Ruby ISO :

6.6 Wartości logiczne

Obiekt jest klasyfikowany do obiektu prawdziwego lub fałszywego .

Tylko fałsz i zero są fałszywymi obiektami. false jest jedynym wystąpieniem klasy FalseClass(patrz 15.2.6), do którego odnosi się wyrażenie fałszywe (patrz 11.5.4.8.3). zero jest jedynym wystąpieniem klasy NilClass(patrz 15.2.4), do którego ewaluuje wyrażenie zerowe (patrz 11.5.4.8.2).

Przedmioty inne niż false i zero są klasyfikowane jako obiekty prawdziwe. true jest jedynym wystąpieniem klasy TrueClass(patrz 15.2.5), do którego odnosi się wyrażenie prawdziwe (patrz 11.5.4.8.3).

Plik wykonywalny Ruby / Spec wydaje się zgadzać :

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

Według tych dwóch źródeł zakładam, że Regexpsą one również zgodne z prawdą , ale według moich testów nie są to:

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

Przetestowałem to na YARV 2.7.0-Preview1 , TruffleRuby 19.2.0.1 i JRuby 9.2.8.0 . Wszystkie trzy implementacje są ze sobą zgodne i nie zgadzają się ze Specyfikacją języka Ruby ISO i moją interpretacją Ruby / Spec.

Mówiąc dokładniej, Regexpobiekty będące wynikiem oceny Regexp literałówfałszem , podczas gdy Regexpobiekty, które są wynikiem jakiegoś innego wyrażenia, są prawdziwe :

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

Czy to błąd lub pożądane zachowanie?

Jörg W Mittag
źródło
Interesujące jest to, że Regex.new("a")jest prawdą.
mrzasa,
!!//jest fałszywe, ale !!/r/prawdziwe. Rzeczywiście dziwne.
maks.
@max !!/r/produkuje falsedla mnie za pomocą (RVM) Ruby 2.4.1.
3limin4t0r
Przepraszam mój zły @ 3limin4t0r. Masz rację. Musiałem zrobić coś naprawdę głupiego, jak pominięcie wykrzyknika.
maks.
2
Hipoteza, myślę, że //in if // thenjest interpretowany jako test (skrót do if //=~nil then) (który zawsze jest fałszem bez względu na wzorzec), a nie jako instancja Regexp.
Casimir et Hippolyte

Odpowiedzi:

6

To nie jest błąd. Ruby przepisuje kod, żeby tak się stało

if /foo/
  whatever
end

skutecznie staje się

if /foo/ =~ $_
  whatever
end

Jeśli uruchamiasz ten kod w normalnym skrypcie (i nie korzystasz z -eopcji), powinieneś zobaczyć ostrzeżenie:

warning: regex literal in condition

Jest to prawdopodobnie nieco mylące przez większość czasu, dlatego pojawia się ostrzeżenie, ale może być przydatne w przypadku jednej linii z -eopcją. Na przykład możesz wydrukować wszystkie wiersze pasujące do danego wyrażenia regularnego z pliku za pomocą

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

(Domyślny argument printto $_również.)

matowy
źródło
Zobacz również -n, -p, -ai -lopcje, jak również kilka metod jądra, które są dostępne tylko wtedy, gdy -nalbo -psą używane ( chomp, chop, gsubi sub).
mat
Istnieje również druga część analizatora składni, w której emitowane jest to ostrzeżenie. Nie wiem jednak, co się tam dzieje.
mat
Uważam, że „druga część” dotyczy tej kwestii. NODE_LITz rodzajem T_REGEXP. Ten, który zamieściłeś w odpowiedzi, dotyczy literału dynamicznegoRegexp , tj. RegexpLiterału, który wykorzystuje interpolację, np /#{''}/.
Jörg W Mittag,
@ JörgWMittag Myślę, że masz rację. Grzebiąc w kompilatorze i generowanym kodzie bajtowym, wygląda na to, że w przypadku dynamicznego wyrażenia regularnego drzewo parsowania jest przepisywane, aby jawnie dodać $_jako węzeł, który kompilator obsługuje normalnie, podczas gdy w przypadku statycznym wszystko jest obsługiwane przez kompilator. To dla mnie wstyd, ponieważ „hej, możesz zobaczyć, gdzie parsowane jest tutaj drzewo”, stanowi dobrą odpowiedź.
mat
4

Jest to wynik (o ile mogę powiedzieć) nieudokumentowanej funkcji języka ruby, co najlepiej tłumaczy ta specyfikacja :

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

Zasadniczo możesz traktować to $_jako „ostatni ciąg przeczytany przez gets

Sprawiając, że sprawy stają się jeszcze bardziej zagmatwane, $_(wraz z $-) nie jest zmienną globalną; ma zasięg lokalny .


Po uruchomieniu skryptu Ruby, $_ == nil.

Tak więc kod:

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

Jest interpretowany w następujący sposób:

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

... Który zwraca falsey.

Z drugiej strony, w przypadku wyrażenia nieliteralnego (np. r = //Lub Regexp.new('')), ta specjalna interpretacja nie ma zastosowania.

//jest prawdą; podobnie jak wszystkie inne przedmioty w rubinie oprócz nili false.


O ile nie uruchomi skryptu ruby ​​bezpośrednio w wierszu polecenia (tj. Z -eflagą), analizator ruby ​​wyświetli ostrzeżenie przed takim użyciem:

ostrzeżenie: wyrażenie dosłowne w wyrażeniu regularnym

Państwo mogli skorzystać z tego zachowania w skrypcie, z czymś takim:

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

... Ale bardziej normalne byłoby przypisanie zmiennej lokalnej do wyniku getsi jawne sprawdzenie wyrażenia regularnego względem tej wartości.

Nie znam żadnego przypadku użycia do wykonania tego sprawdzenia z pustym wyrażeniem regularnym, szczególnie gdy jest on zdefiniowany jako wartość dosłowna. Wyróżniony przez Ciebie wynik naprawdę zaskoczyłby większość deweloperów ruby.

Tom Lord
źródło
Jako przykładu użyłem warunkowego. !// #=> truema takie samo zachowanie i nie jest warunkowe. Nie mogłem znaleźć żadnego kontekstu logicznego (warunkowego lub nie), w którym zachowuje się on zgodnie z oczekiwaniami.
Jörg W Mittag
@ JörgWMittag Masz na myśli np. !// ? true : falseZwroty true? Myślę, że to znowu ten sam punkt - jest interpretowany w następujący sposób:!(// =~ nil) ? true : false
Tom Lord
Jeśli ustawisz ręcznie $_ = 'hello world'przed uruchomieniem powyższego kodu, powinieneś uzyskać inny wynik - ponieważ // =~ 'hello world', ale nie pasuje nil.
Tom Lord
Nie, mam na myśli !// bez ocen warunkowych do true. Podana specyfikacja dotyczy Regexpliterału w warunku, ale w tym przykładzie nie ma warunku, więc ta specyfikacja nie ma zastosowania.
Jörg W Mittag
2
Ach .. Tak, bardzo zaskakujące. Zachowanie wydaje się być powiązane, ale: puts !//; $_ = ''; puts !//- Przypuszczam, ponieważ parser rozwija je jak makro; niekoniecznie musi znajdować się w warunkowym?
Tom Lord