Warum wird ein Regexp-Objekt in Ruby als "falsch" angesehen?

16

Ruby hat eine universelle Vorstellung von " Wahrhaftigkeit " und " Falschheit ".

Ruby hat zwei Klassen spezifische haben für Boolesche Objekte, TrueClassund FalseClassmit Instanzen Singleton durch die speziellen Variablen bezeichnet trueund falsesind.

Allerdings Truthiness und falsiness sind nicht auf Instanzen dieser beiden Klassen beschränkt, ist das Konzept universell und gilt für jedes einzelne Objekt in Ruby. Jedes Objekt ist entweder wahr oder falsch . Die Regeln sind sehr einfach. Insbesondere sind nur zwei Objekte falsch :

Jedes andere Objekt ist wahr . Dies schließt auch Objekte ein, die in anderen Programmiersprachen als falsch gelten , wie z

Diese Regeln sind in die Sprache integriert und nicht benutzerdefinierbar. Es gibt keine to_boolimplizite Konvertierung oder ähnliches.

Hier ist ein Zitat aus der ISO Ruby-Sprachspezifikation :

6.6 Boolesche Werte

Ein Objekt wird entweder als wahres Objekt oder als falsches Objekt klassifiziert .

Nur falsch und null sind falsche Objekte. false ist die einzige Instanz der Klasse FalseClass(siehe 15.2.6), für die ein falscher Ausdruck ausgewertet wird (siehe 11.5.4.8.3). nil ist die einzige Instanz der Klasse NilClass(siehe 15.2.4), für die ein nil-Ausdruck ausgewertet wird (siehe 11.5.4.8.2).

Andere Objekte als false und nil werden in trueish-Objekte eingeteilt. true ist die einzige Instanz der Klasse TrueClass(siehe 15.2.5), für die ein true-Ausdruck ausgewertet wird (siehe 11.5.4.8.3).

Die ausführbare Datei Ruby / Spec scheint zuzustimmen :

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

Nach diesen beiden Quellen würde ich annehmen, dass Regexps auch wahr sind , aber nach meinen Tests sind sie nicht:

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

Ich habe dies auf YARV 2.7.0-Preview1 , TruffleRuby 19.2.0.1 und JRuby 9.2.8.0 getestet . Alle drei Implementierungen stimmen überein und stimmen nicht mit der ISO Ruby-Sprachspezifikation und meiner Interpretation der Ruby / Spec überein.

Etwas präziser, Regexp Objekte, die das Ergebnis der Bewertung von Regexp Literalen sind, falsch , während RegexpObjekte, die das Ergebnis eines anderen Ausdrucks sind, wahr sind :

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

Ist das ein Fehler oder ein gewünschtes Verhalten?

Jörg W Mittag
quelle
Interessant ist, dass Regex.new("a")das wahr ist.
Mrzasa
!!//ist falsch, aber !!/r/wahr. Tatsächlich seltsam.
Max
@max !!/r/produziert falsefür mich mit (RVM) Ruby 2.4.1.
3limin4t0r
Entschuldigung mein schlechtes @ 3limin4t0r. Du hast recht. Ich muss etwas wirklich Dummes getan haben, wie ein Ausrufezeichen wegzulassen.
Max
2
Eine Hypothese, ich denke, dass //in if // thenals Test (eine Abkürzung für if //=~nil then) interpretiert wird (das ist unabhängig vom Muster immer falsch) und nicht als Regexp-Instanz.
Casimir et Hippolyte

Antworten:

6

Das ist kein Fehler. Was passiert ist, dass Ruby den Code so umschreibt, dass

if /foo/
  whatever
end

effektiv wird

if /foo/ =~ $_
  whatever
end

Wenn Sie diesen Code in einem normalen Skript ausführen (und das nicht verwenden) -e Option ), sollte eine Warnung angezeigt werden:

warning: regex literal in condition

Dies ist wahrscheinlich die meiste Zeit etwas verwirrend, weshalb die Warnung ausgegeben wird, kann aber für eine Zeile mit der -eOption nützlich sein . Beispielsweise können Sie alle Zeilen, die einem bestimmten regulären Ausdruck entsprechen, aus einer Datei mit drucken

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

(Das Standardargument für printist $_ebenfalls.)

matt
quelle
Siehe auch -n, -p, -aund -lOptionen, sowie die wenige Kernel - Methoden , die nur verfügbar sind , wenn -noder -pverwendet werden ( chomp, chop, gsubund sub).
Matt
Es gibt auch einen zweiten Teil des Parsers, in dem diese Warnung ausgegeben wird. Ich weiß allerdings nicht, was dort los ist.
Matt
Ich glaube, dass "zweiter Teil" derjenige ist, der tatsächlich für diese Frage gilt. NODE_LITmit Typ T_REGEXP. Diejenige, die Sie in Ihrer Antwort gepostet haben, bezieht sich auf ein dynamisches RegexpLiteral , dh ein RegexpLiteral, das Interpolation verwendet, z /#{''}/.
Jörg W Mittag
@ JörgWMittag Ich denke du hast recht. Beim Stöbern im Compiler und im generierten Bytecode sieht es so aus, als würde im Fall des dynamischen regulären Ausdrucks der Analysebaum neu geschrieben, um ihn explizit $_als Knoten hinzuzufügen , den der Compiler wie gewohnt behandelt, während im statischen Fall alles von der behandelt wird Compiler. Das ist eine Schande für mich, denn „Hey, du kannst sehen, wo der Analysebaum hier umgeschrieben wird“ ist eine nette Antwort.
Matt
4

Dies ist das Ergebnis (soweit ich das beurteilen kann) eines undokumentierten Merkmals der Rubinsprache, das am besten durch diese Spezifikation erklärt wird :

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

Sie können im Allgemeinen denken, $_als die „letzte Zeichenfolge gelesen von gets

Um die Sache noch verwirrender zu machen, ist $_(zusammen mit $-) keine globale Variable; es hat lokalen Geltungsbereich .


Wenn ein Ruby-Skript gestartet wird , $_ == nil.

Also der Code:

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

Wird interpretiert als:

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

... was Falsey zurückgibt.

Andererseits gilt für einen nicht wörtlichen regulären Ausdruck (z. B. r = //oder Regexp.new('')) diese spezielle Interpretation nicht.

//ist wahr; genau wie alle anderen Objekte in Rubin neben nilund false.


Sofern kein Ruby-Skript direkt in der Befehlszeile (dh mit dem -eFlag) ausgeführt wird, zeigt der Ruby-Parser eine Warnung vor einer solchen Verwendung an:

Warnung: Regex-Literal in gutem Zustand

Sie können dieses Verhalten in einem Skript verwenden, beispielsweise mit:

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

... Es wäre jedoch normaler, dem Ergebnis von eine lokale Variable zuzuweisen getsund die Regex-Prüfung explizit für diesen Wert durchzuführen.

Mir ist kein Anwendungsfall für die Durchführung dieser Prüfung mit einem leeren regulären Ausdruck bekannt, insbesondere wenn dieser als Literalwert definiert ist. Das Ergebnis, das Sie hervorgehoben haben, würde in der Tat die meisten Ruby-Entwickler überraschen.

Tom Lord
quelle
Ich habe die Bedingung nur als Beispiel verwendet. !// #=> truehat das gleiche Verhalten und ist nicht in einer Bedingung. Ich konnte keinen booleschen Kontext finden (bedingt oder nicht), in dem er sich wie erwartet verhält.
Jörg W Mittag
@ JörgWMittag Meinst du zB !// ? true : falseRetouren true? Ich denke, das ist wieder der gleiche Punkt - es wird interpretiert als:!(// =~ nil) ? true : false
Tom Lord
Wenn Sie $_ = 'hello world'vor dem Ausführen des obigen Codes manuell festlegen , sollten Sie ein anderes Ergebnis erhalten - weil // =~ 'hello world', aber nicht übereinstimmt nil.
Tom Lord
Nein, ich meine !// ohne die bedingte Auswertung zu true. Bei der von Ihnen angegebenen Spezifikation handelt es sich um ein RegexpLiteral in einer Bedingung. In diesem Beispiel gibt es jedoch keine Bedingung, sodass diese Spezifikation nicht gilt.
Jörg W Mittag
2
Ah .. Ja, sehr überraschend. Das Verhalten scheint jedoch miteinander verbunden zu sein: puts !//; $_ = ''; puts !//- Ich nehme an, weil der Parser es wie ein Makro erweitert; es muss nicht unbedingt in einer Bedingung sein?
Tom Lord