Frage

Ich bin nicht in der Lage, den Sinn zu verstehen Option[T] Klasse in Scala.Ich meine, ich kann keine Vorteile erkennen None über null.

Betrachten Sie zum Beispiel den Code:

object Main{
  class Person(name: String, var age: int){
    def display = println(name+" "+age)
  }

  def getPerson1: Person = {
    // returns a Person instance or null
  }

  def getPerson2: Option[Person] = {
    // returns either Some[Person] or None
  }

  def main(argv: Array[String]): Unit = {
    val p = getPerson1
    if (p!=null) p.display

    getPerson2 match{
      case Some(person) => person.display
      case None => /* Do nothing */
    }
  }
}

Nehmen wir nun an, die Methode getPerson1 kehrt zurück null, dann der Anruf an display in der ersten Zeile von main wird mit Sicherheit scheitern NPE.Ebenso wenn getPerson2 kehrt zurück None, Die display Der Aufruf schlägt erneut mit einem ähnlichen Fehler fehl.

Wenn ja, warum macht Scala dann die Dinge komplizierter, indem es einen neuen Wert-Wrapper einführt (Option[T]), anstatt einem einfachen Ansatz zu folgen, der in Java verwendet wird?

AKTUALISIEREN:

Ich habe meinen Code gemäß bearbeitet @Mitch's Vorschlag.Ich kann immer noch keinen besonderen Vorteil darin erkennen Option[T].Ich muss das Außergewöhnliche testen null oder None in beiden Fällen.:(

Wenn ich das richtig verstanden habe @Michaels Antwort, ist der einzige Vorteil von Option[T] ist, dass es dem Programmierer dies ausdrücklich mitteilt Diese Methode könnte None zurückgeben?Ist dies der einzige Grund für diese Designwahl?

War es hilfreich?

Lösung

Sie werden den Punkt von bekommen Option Besser, wenn Sie sich dazu zwingen, niemals, niemals zu verwenden get. Das ist, weil get ist das Äquivalent von "Ok, schick mich zurück nach Null-Land".

Nehmen Sie also Ihr Beispiel von Ihnen. Wie würden Sie anrufen display ohne zu benutzen get? Hier sind ein paar alternativen:

getPerson2 foreach (_.display)
for (person <- getPerson2) person.display
getPerson2 match {
  case Some(person) => person.display
  case _ =>
}
getPerson2.getOrElse(Person("Unknown", 0)).display

Keines dieser Alternativen lässt Sie anrufen display auf etwas, das nicht existiert.

Wie für warum get Es gibt Scala nicht, wie Ihr Code geschrieben werden soll. Es mag Sie sanft hervorbringen, aber wenn Sie auf kein Sicherheitsnetz zurückgreifen möchten, ist es Ihre Wahl.


Du hast es hier genagelt:

Ist der einzige Vorteil der Option [t], dass er dem Programmierer ausdrücklich mitteilt, dass diese Methode keine zurückgeben könnte?

Bis auf die "einzige". Aber lassen Sie mich das auf andere Weise wiederholen: die hauptsächlich Vorteil von Option[T] Über T ist Type Sicherheit. Es stellt sicher, dass Sie keine senden werden T Methode zu einem Objekt, das möglicherweise nicht existiert, wie der Compiler Sie nicht lässt.

Sie sagten, Sie müssten in beiden Fällen auf Nullbarkeit testen, aber wenn Sie vergessen - oder nicht wissen - müssen Sie nach NULL suchen, wird es Ihnen der Compiler sagen? Oder werden Ihre Benutzer?

Natürlich ermöglicht Scala aufgrund seiner Interoperabilität mit Java Nulls genauso wie Java. Wenn Sie also Java -Bibliotheken verwenden, wenn Sie schlecht geschriebene Skala -Bibliotheken verwenden oder wenn Sie schlecht geschrieben verwenden persönlich Scala -Bibliotheken, Sie müssen sich immer noch mit Null -Zeigern befassen.

Andere zwei wichtige Vorteile von Option Ich kann mir vorstellen, sind:

  • Dokumentation: Eine Signatur vom Typ Methodyp zeigt Ihnen, ob ein Objekt immer zurückgegeben wird oder nicht.

  • Monadische Komposition.

Letzteres dauert viel länger, um vollständig zu schätzen, und es ist nicht gut für einfache Beispiele geeignet, da es nur seine Stärke auf komplexen Code zeigt. Ich werde unten ein Beispiel geben, aber ich bin mir bewusst, dass es kaum etwas bedeuten wird, außer für die Leute, die es bereits bekommen.

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)

Andere Tipps

Vergleichen:

val p = getPerson1 // a potentially null Person
val favouriteColour = if (p == null) p.favouriteColour else null

mit:

val p = getPerson2 // an Option[Person]
val favouriteColour = p.map(_.favouriteColour)

Das monadische Eigentum binden, was in Scala als die erscheint Karte Funktion ermöglicht es uns, Operationen an Objekten zu ketten, ohne sich Sorgen darüber zu machen, ob sie „null“ sind oder nicht.

Nehmen Sie dieses einfache Beispiel etwas weiter. Sagen Sie, wir wollten alle Lieblingsfarben einer Liste von Menschen finden.

// list of (potentially null) Persons
for (person <- listOfPeople) yield if (person == null) null else person.favouriteColour

// list of Options[Person]
listOfPeople.map(_.map(_.favouriteColour))
listOfPeople.flatMap(_.map(_.favouriteColour)) // discards all None's

Oder vielleicht möchten wir den Namen der Schwester der Mutter des Vaters des Vaters finden:

// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister

// with options
val fathersMothersSister = getPerson2.flatMap(_.father).flatMap(_.mother).flatMap(_.sister)

Ich hoffe, dass dies ein wenig Licht hat, wie Optionen das Leben ein wenig einfacher machen können.

Der Unterschied ist subtil. Denken Sie daran, wirklich eine Funktion zu sein muss Rückgabe einen Wert - Null wird in diesem Sinne nicht wirklich als "normaler Rückgabewert" angesehen, eher a Bodentyp/nichts.

In einem praktischen Sinne, wenn Sie eine Funktion nennen, die optional etwas zurückgibt, würden Sie dies tun:

getPerson2 match {
   case Some(person) => //handle a person
   case None => //handle nothing 
}

Zugegeben, Sie können mit Null etwas Ähnliches tun - aber das macht die Semantik des Berufung getPerson2 offensichtlich aufgrund der Tatsache, dass es zurückkehrt Option[Person] (Eine nette praktische Sache, außer sich darauf zu verlassen, dass jemand den Dokument liest und einen NPE bekommt, weil er den Dokument nicht liest).

Ich werde versuchen, einen funktionalen Programmierer zu graben, der eine strengere Antwort geben kann als ich.

Für mich sind Optionen wirklich interessant, wenn sie mit der Verständnissyntax behandelt werden. Einnahme Synesso Vorbei Beispiel:

// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister

// with options
val fathersMothersSister = for {
                                  father <- person.father
                                  mother <- father.mother
                                  sister <- mother.sister
                               } yield sister

Wenn eine der Zuordnung ist None, das fathersMothersSister wird sein None aber nein NullPointerException wird erhoben. Sie können dann sicher passieren fathersMothersSisterzu einer Funktion, die Optionsparameter ohne Sorgen zu sich nehmen. Sie suchen also nicht auf Null und kümmern sich nicht um Ausnahmen. Vergleichen Sie dies mit der Java -Version in Synesso Beispiel.

Sie haben ziemlich leistungsstarke Kompositionsfunktionen mit Option:

def getURL : Option[URL]
def getDefaultURL : Option[URL]


val (host,port) = (getURL orElse getDefaultURL).map( url => (url.getHost,url.getPort) ).getOrElse( throw new IllegalStateException("No URL defined") )

Vielleicht hat jemand anderes darauf hingewiesen, aber ich habe es nicht gesehen:

Ein Vorteil der Musteranpassung mit Option [t] im Vergleich zu Nullüberprüfung ist, dass die Option eine versiegelte Klasse ist. Daher gibt der Scala-Compiler eine Warnung aus, wenn Sie es vernachlässigen, entweder das oder den Niemand zu codieren. Der Compiler hat eine Compiler -Flagge, die Warnungen in Fehler verwandelt. Daher ist es möglich, das Versäumnis, den Fall "nicht existieren" zur Kompilierungszeit und nicht zur Laufzeit zu verarbeiten. Dies ist ein enormer Vorteil gegenüber der Verwendung des Nullwerts.

Es ist nicht da, um einen Null -Check zu vermeiden. Es ist da, um einen Null -Check zu erzwingen. Der Punkt wird klar, wenn Ihre Klasse 10 Felder hat, von denen zwei null sein könnten. Und Ihr System hat 50 ähnliche Klassen. In der Java -Welt versuchen Sie, NPEs auf diesen Feldern zu verhindern, indem Sie eine Kombination aus mentaler HoresFower, Namenskonvention oder vielleicht sogar Anmerkungen verwenden. Und jeder Java -Entwickler scheitert in erheblichem Maße. Die Optionsklasse macht den Entwicklern, die versuchen, den Code zu verstehen, nicht nur "nullierbare" Werte, sondern ermöglicht es dem Compiler, diesen zuvor unausgesprochenen Vertrag durchzusetzen.

Kopiert von Dieser Kommentar durch Daniel Spiewak ]

Wenn die einzige Möglichkeit zu verwenden Option Ich bin mit dem Muster überein, um Werte herauszuholen. Sie vermissen jedoch eine * riesige * Klasse seiner Funktionalität. Der einzige überzeugende Grund zu verwenden Option Is wenn Sie seine Dienstprogrammfunktionen höherer Ordnung verwenden. Effektiv müssen Sie seine monadische Natur verwenden. Zum Beispiel (unter der Annahme einer bestimmten Menge an API -Trimmen):

val row: Option[Row] = database fetchRowById 42
val key: Option[String] = row flatMap { _ get “port_key” }
val value: Option[MyType] = key flatMap (myMap get)
val result: MyType = value getOrElse defaultValue

War das nicht so raffiniert? Wir können tatsächlich viel besser machen, wenn wir verwenden for-kompetenz:

val value = for {
row <- database fetchRowById 42
key <- row get "port_key"
value <- myMap get key
} yield value
val result = value getOrElse defaultValue

Sie werden feststellen, dass wir * nie * explizit auf Null, keine oder eines seiner Ilk prüfen. Der springende Punkt ist es, eine dieser Überprüfungen zu vermeiden. Sie streiten sich nur mit Berechnungen und bewegen sich die Zeile hinunter, bis Sie * wirklich * einen Wert herausholen müssen. Zu diesem Zeitpunkt können Sie entscheiden, ob Sie eine explizite Überprüfung durchführen möchten (was Sie sollten noch nie müssen tun), einen Standardwert geben, eine Ausnahme auswerfen usw.

Ich mache niemals explizite Übereinstimmung gegen Option, und ich kenne viele andere Scala -Entwickler, die sich im selben Boot befinden. David Pollak hat mir erst neulich erwähnt, dass er so explizites Matching verwendet Option (oder Box, im Fall von Lift) als Zeichen, dass der Entwickler, der den Code schrieb, die Sprache und seine Standardbibliothek nicht vollständig versteht.

Ich will kein Trollhammer sein, aber Sie müssen sich wirklich ansehen, wie Sprachfunktionen * tatsächlich * in der Praxis verwendet werden, bevor Sie sie als nutzlos verprügeln. Ich bin absolut einverstanden, dass die Option ziemlich ungewöhnlich ist, da * Sie * sie benutzt haben, aber Sie verwenden sie nicht so, wie sie entworfen wurde.

Ein Punkt, den niemand hier hier angehoben zu haben scheint, ist, dass Sie zwar eine Nullreferenz haben können, es jedoch eine Unterscheidung durch Option ergeben.

Das heißt du kannst haben Option[Option[A]], was bewohnt werden würde von None, Some(None) und Some(Some(a)) wo a ist einer der üblichen Bewohner von A. Dies bedeutet, dass Sie, wenn Sie eine Art Container haben und in der Lage sein möchten, Null -Zeiger darin zu speichern, und sie herausholen möchten, einen zusätzlichen booleschen Wert zurückgeben müssen, um zu wissen, ob Sie tatsächlich einen Wert haben. Waren wie diese reichlich In den Java-Containern können APIs und einige lockfreie Varianten nicht einmal bereitgestellt werden.

null ist eine einmalige Konstruktion, es besteht nicht mit sich selbst zusammen, sie ist nur als Referenztypen verfügbar und zwingt Sie, nicht in der Art zu argumentieren.

Zum Beispiel, wenn Sie überprüfen

if (x == null) ...
else x.foo()

Sie müssen im Kopf in Ihrem Kopf herumtragen else Verzweigen Sie das x != null und dass dies bereits überprüft wurde. Wenn Sie jedoch so etwas wie Option verwenden

x match {
case None => ...
case Some(y) => y.foo
}

Sie kennt y ist nicht Nonedurch Bau - und Sie würden wissen, dass es nicht war null entweder wenn es nicht für Hoare gäbe Milliarden -Dollar -Fehler.

Option [t] ist eine Monadin, die sehr nützlich ist, wenn Sie Funktionen hoher Ordnung verwenden, um Werte zu manipulieren.

Ich empfehle Ihnen, Artikel zu lesen, die unten aufgeführt sind. Sie sind wirklich gute Artikel, die Ihnen zeigen, warum Option [t] nützlich ist und wie sie auf funktionale Weise verwendet werden können.

Hinzufügen von Randall's Teaser einer Antwort, zu verstehen, warum das potenzielle Fehlen eines Wertes durch dargestellt wird durch Option erfordert Verständnis was Option Aktien mit vielen anderen Typen in Scala - insbesondere Typen modellieren Monaden. Wenn man das Fehlen eines Wertes mit Null darstellt, kann diese Unterscheidung für Abwesenheit nicht an den Verträgen der anderen monadischen Typen teilnehmen.

Wenn Sie nicht wissen, was Monaden sind oder nicht bemerken, wie sie in Scalas Bibliothek vertreten sind, werden Sie nicht sehen, was Option Spielt mit und Sie können nicht sehen, was Sie verpassen. Die Verwendung haben viele Vorteile Option Anstelle von Null wäre dies auch in Ermangelung eines Monad -Konzepts bemerkenswert (ich diskutiere einige von ihnen in den "Kosten der Option / einige gegen Null". scala-user Mailinglisten -Thread hier), aber über die Isolation zu sprechen ist so, als würde man über den Iterator -Typ einer bestimmten Leuchtenliste sprechen und sich fragen, warum es notwendig ist, während sie die allgemeinere Schnittstelle zwischen Container/Iterator/Algorithmus verpassen. Auch hier ist eine breitere Schnittstelle bei der Arbeit, und Option Bietet ein Präsenz- und Abschlussmodell dieser Schnittstelle.

Ich denke, der Schlüssel liegt in Synessos Antwort:Option ist nicht In erster Linie nützlich als umständlicher Alias ​​für null, aber auch als vollwertiges Objekt, das Ihnen dann bei Ihrer Logik helfen kann.

Das Problem mit null ist, dass es das ist Mangel eines Objekts.Es gibt keine Methoden, die Ihnen beim Umgang damit helfen könnten (allerdings können Sie als Sprachdesigner immer längere Listen von Funktionen zu Ihrer Sprache hinzufügen, die ein Objekt emulieren, wenn Sie wirklich Lust dazu haben).

Eine Sache, die Option, wie Sie gezeigt haben, tun kann, besteht darin, null zu emulieren;Sie müssen dann auf den außergewöhnlichen Wert „None“ statt auf den außergewöhnlichen Wert „null“ testen.Wenn Sie es vergessen, werden in jedem Fall schlimme Dinge passieren.Die Option verringert die Wahrscheinlichkeit, dass es versehentlich passiert, da Sie „get“ eingeben müssen (was Sie daran erinnern sollte, dass es passiert). könnte sein null, äh, ich meine None), aber das ist ein kleiner Vorteil im Austausch für ein zusätzliches Wrapper-Objekt.

Option zeigt ihre Stärke erst dann wirklich, wenn sie Ihnen dabei hilft, mit dem Konzept umzugehen: Ich wollte etwas, habe aber eigentlich keins.

Betrachten wir einige Dinge, die Sie möglicherweise mit Dingen tun möchten, die möglicherweise null sind.

Möglicherweise möchten Sie einen Standardwert festlegen, wenn Sie eine Null haben.Vergleichen wir Java und Scala:

String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"

Anstelle eines etwas umständlichen ?:Konstrukt haben wir eine Methode, die sich mit der Idee „Verwende einen Standardwert, wenn ich null bin“ befasst.Dadurch wird Ihr Code ein wenig aufgeräumt.

Vielleicht möchten Sie nur dann ein neues Objekt erstellen, wenn Sie einen echten Wert haben.Vergleichen:

File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))

Scala ist etwas kürzer und vermeidet wiederum Fehlerquellen.Bedenken Sie dann den kumulativen Nutzen, wenn Sie Dinge miteinander verketten müssen, wie in den Beispielen von Synesso, Daniel und paradigmatic gezeigt.

Es ist kein groß Verbesserung, aber wenn man alles zusammenzählt, lohnt es sich überall, sehr leistungsstarken Code zu speichern (wo man sogar den winzigen Mehraufwand für die Erstellung des Some(x)-Wrapper-Objekts vermeiden möchte).

Die Match-Nutzung allein ist nicht wirklich hilfreich, außer als Hilfsmittel, um Sie auf den Fall „Null/Keine“ aufmerksam zu machen.Wirklich hilfreich ist es, wenn Sie mit der Verkettung beginnen, z. B. wenn Sie eine Liste mit Optionen haben:

val a = List(Some("Hi"),None,Some("Bye"));
a match {
  case List(Some(x),_*) => println("We started with " + x)
  case _ => println("Nothing to start with.")
}

Jetzt können Sie die None-Fälle und die List-is-empty-Fälle in einer praktischen Anweisung zusammenfassen, die genau den gewünschten Wert ergibt.

NULL -Rückgabeteile sind nur für die Kompatibilität mit Java vorhanden. Sie sollten sie sonst nicht verwenden.

Es ist wirklich eine Frage im Programmierstil. Mit funktionaler Java oder durch Schreiben Ihrer eigenen Helfermethoden können Sie Ihre Optionsfunktionalität haben, aber die Java -Sprache nicht aufgeben:

http://functionaljava.org/examples/#option.bind

Nur weil Scala es standardmäßig enthält, macht es es nicht zu etwas Besonderem. Die meisten Aspekte der funktionalen Sprachen sind in dieser Bibliothek verfügbar und können gut mit anderen Java -Code koexistieren. So wie Sie Scala mit Nulls programmieren können, können Sie Java ohne sie programmieren.

Im Voraus zugeben, dass es sich um eine schelmische Antwort handelt, ist die Option eine Monadin.

Eigentlich teile ich den Zweifel mit Ihnen. Über die Option stört es mich wirklich, dass 1) es einen Leistungsaufwand gibt, da es eine Menge "einige" Wrapper gibt, die alles erstellt haben. 2) Ich muss eine Menge und Option in meinem Code verwenden.

Um Vor- und Nachteile dieser Entscheidung des Sprachentwurfs zu sehen, sollten wir Alternativen berücksichtigen. Da Java das Problem der Nullabilität nur ignoriert, ist es keine Alternative. Die tatsächliche Alternative bietet eine Fantom -Programmiersprache. Es gibt dort nullbare und nicht nullbare Typen und? ?: Operatoren anstelle von Scalas Karte/Flatmap/Getorelse. Ich sehe die folgenden Kugeln im Vergleich:

Vorteil der Option:

  1. Einfachere Sprache - Keine zusätzlichen Sprachkonstrukte erforderlich
  2. Uniform mit anderen monadischen Typen

Nullable -Vorteil:

  1. kürzere Syntax in typischen Fällen
  2. Bessere Leistung (da Sie keine neuen Option Objekte und Lambdas für MAP erstellen müssen, flatMap)

Hier gibt es also keinen offensichtlichen Gewinner. Und noch eine Notiz. Es gibt keinen wichtigsten syntaktischen Vorteil für die Verwendung der Option. Sie können so etwas definieren wie:

def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)

Oder verwenden Sie einige implizite Conversions, um eine pritty Syntax mit Punkten zu erhalten.

Der eigentliche Vorteil von expliziten Optionstypen besteht darin, dass Sie dazu in der Lage sind nicht Verwenden Sie sie in 98% aller Orte und verhindern Sie somit null Ausnahmen. (Und in den anderen 2% erinnert das Typsystem Sie daran, ordnungsgemäß zu überprüfen, wenn Sie tatsächlich darauf zugreifen.)

Eine andere Situation, in der die Option funktioniert, liegt in Situationen, in denen Typen keinen Nullwert haben können. Es ist nicht möglich, Null in einem int, float, doppelt usw. zu speichern, aber mit einer Option können Sie die None verwenden.

In Java müssten Sie die Boxversionen (Ganzzahl, ...) dieser Typen verwenden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top