Warum hat Ruby TrueClass und FalseClass anstelle einer einzelnen Booleschen Klasse?

73

Ich habe daran gearbeitet, Werte zu serialisieren, als ich davon erfuhr. Ruby hat eine TrueClassKlasse und eine FalseClassKlasse, aber es gibt keine BooleanKlasse. Ich würde gerne wissen, warum das so ist.

Ich sehe einige Vorteile in der Verwendung von a Boolean; Beispielsweise könnte das Parsen von Zeichenfolgen darauf zentralisiert werden.

Ruby-Entwickler sind schlauer als ich, daher muss es viele gute Gründe geben, die ich einfach nicht sehe. Aber im Moment sieht es für mich so aus, als hätte ich OneClassein TwoClassstatt Fixnum.

Kikito
quelle
Verwenden andere Sprachen (die nicht so gerne Enten tippen) Boolean?
Andrew Grimm
1
Aus dem Kopf: C ++, Java, C #, Python & Javascript. Andererseits verwenden C und Perl keinen Booleschen Wert.
Kikito
5
C99 hat einen booleschen Typ, boolaber Sie müssen ihn einschließen, stdbool.hum darauf zuzugreifen (oder ihn zu verwenden _Bool).
Mu ist zu kurz

Antworten:

61

Der Zweck einer Klasse besteht darin, ähnliche Objekte oder Objekte mit ähnlichem Verhalten zu gruppieren. 1und 2sind sehr ähnlich, daher ist es vollkommen sinnvoll, dass sie in derselben Klasse sind. trueund sind falsejedoch nicht ähnlich. In der Tat ist ihr springender Punkt, dass sie genau das Gegenteil voneinander sind und ein entgegengesetztes Verhalten haben. Daher gehören sie nicht zur selben Klasse.

Können Sie ein Beispiel dafür geben, welche Art von allgemeinem Verhalten Sie in einer BooleanKlasse implementieren würden ? Mir fällt nichts ein.

Schauen wir uns das Verhalten an TrueClassund FalseClasshaben: Es gibt genau vier Methoden. Nicht mehr. Und in jedem Einzelfall machen die beiden Methoden genau das Gegenteil . Wie und warum würden Sie das in eine einzelne Klasse einordnen?

So implementieren Sie all diese Methoden:

class TrueClass
  def &(other)
    other
  end

  def |(_)
    self
  end

  def ^(other)
    !other
  end

  def to_s
    'true'
  end
end

Und jetzt umgekehrt:

class FalseClass
  def &(_)
    self
  end

  def |(other)
    other
  end

  def ^(other)
    other
  end

  def to_s
    'false'
  end
end

Zugegeben, in Ruby gibt es eine Menge "Magie", die sich hinter den Kulissen abspielt und die nicht wirklich von dem Dolmetscher gehandhabt TrueClassund FalseClassvielmehr fest verdrahtet wird. Sachen wie if, &&, ||und !. In Smalltalk, von dem Ruby viel geliehen hat, einschließlich des Konzepts von FalseClassund TrueClass, werden all diese Methoden ebenfalls als Methoden implementiert, und Sie können dasselbe in Ruby tun:

class TrueClass
  def if
    yield
  end

  def ifelse(then_branch=->{}, _=nil)
    then_branch.()
  end

  def unless
  end

  def unlesselse(_=nil, else_branch=->{})
    ifelse(else_branch, _)
  end

  def and
    yield
  end

  def or
    self
  end

  def not
    false
  end
end

Und wieder umgekehrt:

class FalseClass
  def if
  end

  def ifelse(_=nil, else_branch=->{})
    else_branch.()
  end

  def unless
    yield
  end

  def unlesselse(unless_branch=->{}, _=nil)
    ifelse(_, unless_branch)
  end

  def and
    self
  end

  def or
    yield
  end

  def not
    true
  end
end

Vor ein paar Jahren habe ich das oben genannte nur zum Spaß geschrieben und sogar veröffentlicht . Abgesehen von der Tatsache, dass die Syntax anders aussieht, weil Ruby spezielle Operatoren verwendet, während ich nur Methoden verwende, verhält es sich genau wie die eingebauten Operatoren von Ruby. Tatsächlich habe ich die RubySpec-Konformitätstestsuite übernommen und auf meine Syntax portiert, und sie besteht.

Jörg W Mittag
quelle
9
Vielen Dank, Ihr Kommentar ist sehr umfangreich. Ich habe bereits ein Beispiel für allgemeines Verhalten gegeben - das Parsen von Zeichenfolgen wie in Boolean.parse ("true") => true. Sie haben den Punkt, dass sie 4 Methoden haben und das Gegenteil tun. Und doch kann ich nicht zugeben, dass sie nicht ähnlich sind; Immerhin true.methods == false.methods. Beide gehen und klingen methodisch wie Enten.
Kikito
14
Der Zweck einer Klasse besteht darin, ähnliche Objekte (unter anderem) zu gruppieren. Ich stimme zu - und die Tatsache, dass diese beiden Klassen genau die gleichen vier Methoden haben, legt mir nahe, dass sie möglicherweise den gleichen Typ haben. Dass sie keine Implementierung gemeinsam nutzen, ist ein Problem bei der Methodenvererbung und kein Typisierungsproblem.
Alexloh
5
@alexloh: Ich habe nie gesagt, dass sie nicht den gleichen Typ haben. Ich stimme Ihnen zu : Sie tun die gleiche Art haben. Das hat aber nichts mit ihren Klassen zu tun: Ruby ist eine objektorientierte Sprache. In einer objektorientierten Sprache, die Art (en) ein Objekts wird / werden durch sein Protokoll (e) definiert ist , nicht seine Klasse, im Gegensatz zu ADT orientierten Sprachen wie Java, C # oder C ++ , wo Klassen sind Typen, aber das doesn‘ t gilt nicht für Ruby, da es OO nicht ADT-orientiert ist. In einer objektorientierten Sprache, der Zweck einer Klasse ist die Umsetzung Sharing, aber trueund false nicht jede Implementierung teilen.
Jörg W Mittag
@kikito: Ist das besser evalals so etwas zu tun !!eval("true")? Ich persönlich würde es vorziehen, .to_bool für alles oder sogar bool () zu haben, aber es ist in Ordnung.
Alanjds
@AlanJustinoDaSilva: Ich habe keine Ahnung von dem Bewertungsteil. In Ruby hat alles ein to_bool - es ist nur so, dass alles wahr ist, außer null und falsch :)
kikito
23

Es scheint, dass Matz selbst diese Frage 2004 in einer Mailinglisten-Nachricht beantwortet hat .

Kurzfassung seiner Antwort: "Im Moment funktioniert es in Ordnung, das Hinzufügen eines Booleschen Werts bringt keinen Vorteil."

Persönlich stimme ich dem nicht zu; Das oben erwähnte "String-Parsing" ist ein Beispiel. Eine andere ist, dass, wenn Sie eine Variable je nach Typ unterschiedlich behandeln (dh einen yml-Parser), eine "Boolesche" Klasse hat, dies praktisch ist - sie entfernt ein "wenn". Es sieht auch korrekter aus, aber das ist eine persönliche Meinung.

Kikito
quelle
Ich denke, es entspricht der Ruby-Philosophie, dass wenn Sie eine Boolesche Klasse verpassen und Ruby sie nicht anbietet, Sie sie einfach selbst definieren müssen!
jp
5
Ich sehe nicht, wie ich könnte. Ich weiß, dass Klassen offen sind, aber ... ich bin nicht sicher, ob ihre Hierarchie ist. Richtig wissen, dass TrueClass und FalseClass direkt von Object abhängen. Ich müsste meine Boolesche Klasse dazwischen einfügen. Ich halte das nicht für möglich, würde mich aber freuen, wenn Sie mir das Gegenteil beweisen würden.
Kikito
2
Es scheint mir, dass TrueClass und FalseClass beide Unterklassen von Boolean sind und Ihnen das Beste aus beiden Welten bieten: Unterschiedliche Klassen für ihr unterschiedliches Verhalten, aber eine gemeinsame Basisklasse, zu der Sie gemeinsames Verhalten / Implementierung hinzufügen können. Es würde Sie auch Dinge wie true.is_a tun lassen? Boolean, die sich derzeit vermisst fühlen. Aber wie @kikito sagte, müsste dies auf Sprachebene geändert werden; Wir können die Superklasse einer Klasse zur Laufzeit nicht ändern.
Tyler Rick
Gleichzeitig kann ich Matz 'Argument sehen: Wahr ist nur ein repräsentativer Wert der Wahrheit. "string" und 5 sind ebenfalls wahr. "In Ruby verhält sich alles wie ein Boolescher Wert" [ ruby-forum.com/topic/4412411]
Tyler Rick
3
@kikito: Ich denke, es könnte mit Modulen gemacht werden. Siehe ruby-forum.com/topic/60154#56346 : module Boolean; end [true, false] .each do | obj | obj.extend (Boolean) Ende
Tyler Rick
5

Zitat von Matz im Ruby Forum (2013) :

... Es gibt nichts Richtiges und Falsches, also keine Boolesche Klasse. Außerdem verhält sich in Ruby alles wie ein Boolescher Wert ....

steenslag
quelle
4

true und false könnten von einer Booleschen Klasse verwaltet werden, die mehrere Werte enthält. Dann müsste das Klassenobjekt jedoch interne Werte haben und daher bei jeder Verwendung von der Referenz befreit werden.

Stattdessen behandelt Ruby true und false als lange Werte (0 und 1), von denen jeder einem Typ einer Objektklasse (FalseClass und TrueClass) entspricht. Durch die Verwendung von zwei Klassen anstelle einer einzelnen Booleschen Klasse benötigt jede Klasse keine Werte und kann daher einfach durch ihre Klassenkennung (0 oder 1) unterschieden werden. Ich glaube, dies führt zu erheblichen Geschwindigkeitsvorteilen innerhalb der Ruby-Engine, da Ruby intern TrueClass und FalseClass als lange Werte behandeln kann, für die keine Übersetzung von ihrem ID-Wert erforderlich ist, während ein Boolesches Objekt de-referenziert werden müsste, bevor es ausgewertet werden kann .

Asher
quelle
2
Das ergibt für mich keinen Sinn. Sie sagen, wenn wir eine Boolesche Klasse mit zwei Werten anstelle von TrueClass und FalseClass mit jeweils einem Wert hätten, könnten true und false keine unmittelbaren Werte mehr sein. Warum sollte das so sein? Immerhin gibt es mehr als eine Instanz von Fixnum (deutlich mehr) und Fixnums sind immer noch unmittelbare Werte. Auch kleiner Nitpick: wahr und falsch werden intern nicht als 0 und 1 dargestellt, sondern als 2 und 0 (zumindest in der MRT).
sepp2k
Guter Punkt bei 2 und 0 - Ich war mir bei FalseClass sicher, aber nicht bei TrueClass ... Auf jeden Fall war mein Punkt, dass die Verwendung von TrueClass und FalseClass die Auswertung des Werts einfach über die Klassenkennung ermöglicht, während eine Kennung eine kennzeichnet Das Objekt als Boolesche Klasse müsste dann ausgewertet werden, bevor der Instanzwert bekannt ist. Ich gehe davon aus, dass dies die gleiche Art der Optimierung ist, bei der alle IDs (die im Allgemeinen Zeigeradressen sind) als Longs behandelt werden, sodass FixNums um 1 Bit versetzt und direkt bearbeitet werden können, anstatt vor der Auswertung dereferenziert werden zu müssen.
Asher
2
@Asher: Der Wert wird nicht "einfach über die Klassenkennung ausgewertet". Ruby weiß nicht, dass ein Wert falsch ist, da seine Klasse FalseClass ist. Es weiß, dass es falsch ist, weil der Wert 0 ist. Ebenso weiß es, dass ein Wert wahr ist, weil es 2 ist. Es speichert nicht einmal die Klasseninformationen irgendwo (es gibt tatsächlich keine Objektstruktur, die mit wahr oder falsch assoziiert ist, also würde es eine geben nirgendwo die Klasse zu speichern). Mit anderen Worten, das Ersetzen von TrueClass und FalseClass durch Boolean würde keinen Leistungsunterschied bewirken.
sepp2k
Aber genau das war mein Punkt. Ruby muss TrueClass oder FalseClass nicht instanziieren, da 0 und 2 bereits ihren Werten entsprechen, die auch ihre Klasse sind. Wenn es eine Boolesche Klasse gäbe, wären Instanzen und Instanzbewertung erforderlich (über interne Klassenauflösung). So wie es ist, muss nur der lange Wert (0 oder 2) als Klassen-ID ausgewertet werden. An diesem Punkt kann die Auswertung der Instanz gestoppt werden. 0 und 2 sind direkt und bereits die Klasseninformationen, weshalb nirgendwo etwas anderes gespeichert werden muss.
Asher
Ich sehe nicht, wie eine Instanzbewertung für eine Boolesche Klasse notwendig wäre. Machen Sie Boolean zu einer Klasse mit nur zwei möglichen Werten, die als Konstanten gespeichert werden: true und false. true wird durch den Wert 2 und false durch 0 dargestellt. Boolean.new gibt nur eine dieser beiden Konstanten zurück. Hier ist keine Instanzbewertung erforderlich.
Ragmaanir
3

Da in Ruby standardmäßig alles außer falseund nilals wahr ausgewertet wird, müssen Sie nur das Parsen zu String hinzufügen.

So etwas könnte funktionieren:

class Object

  ## Makes sure any other object that evaluates to false will work as intended,
  ##     and returns just an actual boolean (like it would in any context that expect a boolean value).
  def trueish?; !!self; end

end

class String

  ## Parses certain strings as true; everything else as false.
  def trueish?
    # check if it's a literal "true" string
    return true if self.strip.downcase == 'true'

    # check if the string contains a numerical zero
    [:Integer, :Float, :Rational, :Complex].each do |t|
      begin
        converted_number = Kernel.send(t, self)
        return false if converted_number == 0
      rescue ArgumentError
        # raises if the string could not be converted, in which case we'll continue on
      end
    end

    return false
  end

end

Bei Verwendung würde dies Ihnen Folgendes geben:

puts false.trueish?   # => false
puts true.trueish?    # => true
puts 'false'.trueish? # => false
puts 'true'.trueish?  # => true
puts '0'.trueish?     # => false
puts '1'.trueish?     # => true
puts '0.0'.trueish?   # => false
puts '1.0'.trueish?   # => true

Ich glaube, ein Teil der „großen Idee“ hinter Ruby besteht darin, das gewünschte Verhalten Ihrem Programm eigen zu machen (z. B. Boolesches Parsen) und stattdessen eine vollständig gekapselte Klasse zu erstellen, die in ihrer eigenen Namespace-Welt lebt (z. B. BooleanParser).

Slipp D. Thompson
quelle
2
Ich denke, dass eine größere Idee für Rubin darin besteht, die häufigen Aufgaben zu vereinfachen. Take String - Die meisten seiner Methoden könnten in Bezug auf reguläre Ausdrücke, gsub und monkeypatching implementiert werden, aber sie sind stattdessen bereits in der Klasse "eingebrannt", was nur praktisch ist. Es ist ein bisschen seltsam, dass Boolesche nicht der gleichen Behandlung würdig sind. Trotzdem könnte Ihr Beitrag für jemand anderen nützlich sein, also +1.
Kikito
Ist das nicht die große Idee hinter dem Programmieren? ;-D
Slipp D. Thompson
1
@ SlippD.Thompson Ich meine den Zeichenfolgenwert, wenn ich ihn in "doppeltes Anführungszeichen" schreibe. Aber danke; neu formuliert und geklärt: Gibt es eine Einigung darüber, was gleich falsch ist? Vielleicht auch die Zeichenfolge "nil" und die Zeichenfolge "null" und das Symbol: null und einige andere Dinge? Dumm, ich weiß. Jeder vorgefertigte boolesche Parser würde dem Benutzer Meinungen aufzwingen und versuchen, den integrierten Parser zu verwenden und Fehler zu erhalten, anstatt die Domäne zu betrachten und die Verantwortung für das Parsen zu übernehmen.
Simon B.
1
@ SimonB. Wenn Sie nach einem bestehenden Standard für wahrheitsgemäße Zeichenfolgen suchen, sind Ihre Optionen begrenzt. JavaScript entspricht "1"bei Verwendung nur true ==. Alle anderen Zeichenfolgen sind falsch. PHP ist fast das Gegenteil - "0"und "" ==falsch; Alle anderen Zeichenfolgen sind wahr. Perl & Python (AFAIK) funktionieren wie Ruby - alle Zeichenfolgen sind wahr. In Java erhalten Boolean.parseBoolean()Sie "true"für jede andere Zeichenfolge true und false. C # Boolean.Parse()gibt Ihnen (nach dem Leerzeichen-Strippen und Downcasing) wahr "true", falsch für "false"und wirft auf alles andere.
Slipp D. Thompson
1
@ SimonB. Daher sind Ihre Konformitätsoptionen hauptsächlich durch das Design begrenzt. Ich schlage vor, Ihre Erweiterung entweder hochgradig und offensichtlich klassen- oder app-spezifisch zu machen oder ihr einen funky Methodennamen (z. B. trueish?) zu geben, der deutlich macht, dass die mit dieser Methode getroffenen Annahmen locker und ad hoc sind und nicht auf einem Standard basieren.
Slipp D. Thompson
1

In Ruby sind null und falsch falsch und alles andere ist wahr. Daher ist keine bestimmte boolesche Klasse erforderlich.

Du kannst es versuchen :

if 5
  puts "5 is true"
end

5 ergibt wahr

if nil
    puts "nil is true"
else
    puts "nil is false"
end

Gibt "nil is false" aus

Mongus Pong
quelle
3
@ Mongus Pong: Sie haben vergessen, dass falsedas auch falsch ist :)
alex.zherdev
Richtig, null und falsch ist falsch. Alles andere ist wahr.
Mongus Pong
3
Wie können Sie daraus schließen, dass keine boolesche Klasse benötigt wird, weil nur null und falsch "falsch" ("falsch") sind? Ich kann die Logik hier nicht sehen (Wortspiel beabsichtigt :))
Kikito
@egarcia, weil dies bedeutet, dass jeder Typ verwendet werden kann, um einen Booleschen Wert darzustellen. In Verbindung mit der dynamischen Typisierung von Ruby werden alle erforderlichen Szenarien abgedeckt.
Mongus Pong
1
@ Andrew, verdammt noch mal die englische Sprache! Ich wollte sagen "Neutrino, es ist wahr, was Sie gesagt haben, null und falsch ist falsch.
Mongus Pong
1

Der Hauptgrund ist einfach die Tatsache, dass die Implementierung von Booleschen Ausdrücken viel schneller und einfacher ist als derzeit als bei einer Booleschen Klasse, die eine Konvertierung implizieren würde.

Wie Mongus Pong Ihnen sagte, wenn Sie "wenn" schreiben, bitten Sie den Dolmetscher, die Sache zu bewerten und dann zu verzweigen. Wenn Sie eine Boolesche Klasse hätten, müssten Sie die Auswertung der Sache vor dem Verzweigen in einen Booleschen Wert konvertieren (ein weiterer Schritt).

Denken Sie daran, dass eine solche -> Boolesche Konvertierung als Ruby-Methode in der Booleschen Klasse verfügbar ist. Diese Methode könnte dann dynamisch wie jede andere Ruby-Methode geändert werden, sodass Entwickler die Dinge völlig durcheinander bringen können (was in der Tat nicht so ernst ist), aber dies würde es dem Interpret eindeutig nicht ermöglichen, die Tests so zu optimieren, wie sie sollten.

Ist Ihnen klar, dass es einige CPU-Anweisungen durch einen vollständigen Methodenaufruf ersetzen würde, was in Ruby kostspielig ist (denken Sie an die Verarbeitung der "send" -Methode) ...

Chucky
quelle
Es tut mir leid, aber ich folge nicht den Effizienzgründen. Ich stelle mir vor, dass die Tatsache, dass Sie bei einer Methode nicht "self" aufrufen und stattdessen einen Wert zurückgeben müssen, nur einen sehr geringen Vorteil hat, aber das ist nicht relevant, wenn die gesamte Klasse in C implementiert ist, oder?
Kikito
4
Warum würde eine Boolesche Klasse eine Konvertierung implizieren, TrueClass und FalseClass jedoch nicht? "Wenn Sie eine Boolesche Klasse hätten, müssten Sie die Auswertung von thingin eine Boolesche Klasse konvertieren, bevor Sie verzweigen." Welche Logik steckt hinter dieser Anweisung?
sepp2k
-2

Wie andere gesagt haben, könnten Sie Ruby "patchen". Erstelle deine eigene Klasse. Hier ist etwas, das ich mir ausgedacht habe. Die Methoden in der Booleschen Klasse sind etwas albern, könnten aber irgendwann programmgesteuert nützlich sein.

class Boolean
  def self.new(bool)
    bool
  end

  def self.true
    true
  end

  def self.false
    false
  end
end

class FalseClass
  def is_a?(other)
    other == Boolean || super
  end

  def self.===(other)
    other == Boolean || super
  end
end

class TrueClass
  def is_a?(other)
    other == Boolean || super
  end

  def self.===(other)
    other == Boolean || super
  end
end
Volte
quelle
1
Dies beantwortet nicht die Frage
Felipe