Perché un oggetto Regexp è considerato "falso" in Ruby?


16

Ruby ha un'idea universale di " verità " e " falsità ".

Rubino ha avere due classi specifiche per oggetti booleana, TrueClasse FalseClass, con istanze singleton denotati dalle variabili speciali truee false, rispettivamente.

Tuttavia, la verità e la falsità non si limitano ai casi di quelle due classi, il concetto è universale e si applica a ogni singolo oggetto in Ruby. Ogni oggetto è o verità o falsità . Le regole sono molto semplici. In particolare, solo due oggetti sono falsi :

Ogni singolo altro oggetto è veritiero . Ciò include anche oggetti considerati falsi in altri linguaggi di programmazione, come ad esempio

Queste regole sono integrate nella lingua e non sono definibili dall'utente. Non c'è to_boolconversione implicita o qualcosa di simile.

Ecco una citazione dalla specifica ISO Ruby Language :

6.6 Valori booleani

Un oggetto è classificato in un oggetto trueish o in un oggetto false .

Solo false e zero sono oggetti falsi. false è l'unica istanza della classe FalseClass(vedi 15.2.6), alla quale valuta una falsa espressione (vedi 11.5.4.8.3). nil è l'unica istanza della classe NilClass(vedi 15.2.4), a cui valuta un'espressione nil (vedi 11.5.4.8.2).

Gli oggetti diversi da false e zero sono classificati in oggetti trueish. true è l'unica istanza della classe TrueClass(vedi 15.2.5), alla quale valuta un'espressione vera (vedi 11.5.4.8.3).

L'eseguibile Ruby / Spec sembra essere d'accordo :

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

Secondo queste due fonti, suppongo che anche le Regexps siano vere , ma secondo i miei test non lo sono:

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

Ho provato questo su YARV 2.7.0-preview1 , TruffleRuby 19.2.0.1 e JRuby 9.2.8.0 . Tutte e tre le implementazioni sono in accordo tra loro e non sono d'accordo con la specifica ISO Ruby Language e la mia interpretazione del Ruby / Spec.

Più precisamente, gli Regexpoggetti che sono il risultato della valutazione dei Regexp valori letterali sono falsi , mentre gli Regexpoggetti che sono il risultato di un'altra espressione sono veri :

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

È un bug o un comportamento desiderato?


La cosa interessante è che Regex.new("a")è vero.
mrzasa,

!!//è falso ma !!/r/è vero. Davvero strano.
massimo

@max !!/r/produce falseper me utilizzando (RVM) Ruby 2.4.1.
3limin4t0r

Mi dispiace mio cattivo @ 3limin4t0r. Hai ragione. Devo aver fatto qualcosa di veramente stupido come lasciare un segno di esclamazione.
massimo

2
Un'ipotesi, penso che //in if // thensia interpretato come un test (una scorciatoia per if //=~nil then) (che è sempre falsa qualunque sia il modello) e non come un'istanza Regexp.
Casimir et Hippolyte,

Risposte:


6

Questo non è un bug. Quello che sta succedendo è che Ruby sta riscrivendo il codice in modo che

if /foo/
  whatever
end

diventa effettivamente

if /foo/ =~ $_
  whatever
end

Se stai eseguendo questo codice in uno script normale (e non -estai utilizzando l' opzione), dovresti visualizzare un avviso:

warning: regex literal in condition

Questo è probabilmente un po 'confuso il più delle volte, motivo per cui viene dato l'avvertimento, ma può essere utile per una riga usando l' -eopzione. Ad esempio è possibile stampare tutte le righe corrispondenti a una data regexp da un file con

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

(Anche l'argomento predefinito printè $_.)


Vedi anche -n, -p, -ae -lle opzioni, così come la manciata di metodi kernel che sono disponibili solo quando -no -psono utilizzati ( chomp, chop, gsube sub).
matt

C'è anche una seconda parte del parser in cui viene emesso questo avviso. Non so cosa stia succedendo lì però.
matt

Credo che la "seconda parte" sia quella che si applica effettivamente a questa domanda. NODE_LITcon il tipo T_REGEXP. Quello che hai pubblicato nella tua risposta è per un letterale dinamicoRegexp , cioè un Regexpletterale che utilizza l'interpolazione, ad es /#{''}/.
Jörg W Mittag,

@ JörgWMittag Penso che tu abbia ragione. Frugando nel compilatore e nel bytecode generato, sembra che nel caso della regexp dinamica l'albero di analisi sia riscritto per aggiungere esplicitamente $_come nodo che il compilatore gestisce normalmente, mentre nel caso statico è tutto gestito dal compilatore. Il che è un peccato per me perché "ehi, puoi vedere dove viene riscritto l'albero di analisi" è una buona risposta.
matt

4

Questo è il risultato di (per quanto ne so) una caratteristica non documentata del linguaggio ruby, che è meglio spiegato da questa specifica :

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

In genere puoi pensare a $_come "l'ultima stringa letta da gets"

Rendere le cose ancora più confuse $_(insieme a $-) non è una variabile globale; ha portata locale .


Quando uno script Ruby inizia, $_ == nil.

Quindi, il codice:

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

Viene interpretato come:

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

... Che restituisce falsey.

D'altra parte, per una regexp non letterale (es. r = //O Regexp.new('')), questa speciale interpretazione non si applica.

//è vero; proprio come tutti gli altri oggetti in rubino oltre nile false.


A meno che non esegua uno script ruby ​​direttamente sulla riga di comando (cioè con la -ebandiera), il parser ruby ​​visualizzerà un avviso contro tale utilizzo:

attenzione: regex letterale in condizione

È possibile utilizzare questo comportamento in uno script, con qualcosa del tipo:

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

... Ma sarebbe più normale assegnare una variabile locale al risultato getsed eseguire il controllo regex su questo valore in modo esplicito.

Non sono a conoscenza di alcun caso d'uso per eseguire questo controllo con una regex vuota , specialmente se definito come valore letterale. Il risultato che hai messo in evidenza catturerebbe davvero alla sprovvista la maggior parte degli sviluppatori di rubini.


Ho usato solo il condizionale come esempio. !// #=> trueha lo stesso comportamento e non è condizionale. Non sono riuscito a trovare alcun contesto booleano (condizionale o no), dove si comporta come previsto.
Jörg W Mittag,

@ JörgWMittag Intendi ad esempio i !// ? true : falseresi true? Penso che questo sia di nuovo lo stesso punto - viene interpretato come:!(// =~ nil) ? true : false
Tom Lord,

Se imposti manualmente $_ = 'hello world'prima di eseguire il codice precedente, dovresti ottenere un risultato diverso // =~ 'hello world', perché , ma non corrisponde nil.
Tom Lord,

No, intendo !// senza la valutazione condizionale di true. La specifica che hai citato riguarda un Regexpletterale in un condizionale, ma in questo esempio non esiste un condizionale, quindi questa specifica non si applica.
Jörg W Mittag,

2
Ah .. Sì, molto sorprendente. Il comportamento sembra essere collegato, però: puts !//; $_ = ''; puts !//- suppongo perché il parser lo espande come una macro; non deve necessariamente essere all'interno di un condizionale?
Tom Lord,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.