Optionswert abrufen oder Ausnahme auslösen


76

Was ist bei einer Option die idiomatische Methode, um ihren Wert zu ermitteln oder eine Ausnahme auszulösen?

def foo() : String = {
  val x : Option[String] = ...
  x.getOrException()
}

12
Warum nicht .get?
Geheimnisvoller Dan

@ MySeriousDan Ich bin gerade auf diesen Fall gestoßen. Ich habe eine Situation, in der ich absolut 100% sicher bin, dass es einige zurückgeben muss, und wenn nicht, ist es ein ernstes Problem und muss werfen. Aber ich möchte meine eigene Ausnahme und alle mehrdeutigen NoSuchElementExceptionauslösen. Außerdem kann diese Methode die meiste Zeit None sicher zurückgeben.
Kai Sellgren

1
Die eigentliche Frage ist, warum Ihr Typ sagt, dass er möglicherweise fehlt, wenn Sie wissen, dass dies nicht der Fall ist. Wenn Sie Ihr Wissen in Typen ausdrücken können, ist dies im Allgemeinen am besten. Es kann sein, dass Sie Ihre Klassen ein wenig umgestalten, aber auf lange Sicht ist es im Allgemeinen viel angenehmer.
Geheimnisvoller Dan

1
Die Antwort von @TravisBrown sollte die akzeptierte sein, nicht meine. Meins ist nicht idiomatisch, ich habe es geschrieben, als ich noch ein "Neuling" war (ich bin es immer noch, aber Level 2). Lassen Sie es als Beispiel stehen, was nicht zu tun ist ... ;-)
Sebastian N.

Sie machen es falsch, wenn Sie eine Ausnahme auslösen müssen. Die idiomatische Möglichkeit, Option zu verwenden, besteht darin, None zurückzugeben und überhaupt keine Ausnahme auszulösen.
Priyank Desai

Antworten:


46

(BEARBEITEN: Dies ist nicht die beste oder idiomatischste Art, dies zu tun. Ich habe es geschrieben, als ich mit Scala nicht vertraut war. Ich lasse es hier als Beispiel dafür, wie man es nicht macht. Heutzutage würde ich es als @TravisBrown tun.)

Ich denke, es läuft wirklich auf zwei Dinge hinaus:

  • Wie sicher bist du dir, dass der Wert da ist?
  • Wie möchten Sie reagieren, wenn dies nicht der Fall ist?

Wenn Sie zu diesem Zeitpunkt in Ihrem Code erwarten , dass der Wert vorhanden ist, und in dem Remote-Fall, dass Ihr Programm nicht schnell fehlschlagen soll , würde ich nur einen normalen Vorgang ausführen getund Scala einen auslösen lassen, NoSuchElementExceptionwenn kein Wert vorhanden wäre ::

def foo (): String = {
  val x: Option [String] = ...
  x.get
}}

Wenn Sie den Fall anders behandeln möchten (werfen Sie Ihre eigene Ausnahme), würde ein eleganterer Weg meiner Meinung nach so aussehen:

def foo (): String = {
  val x: Option [String] = Keine
  x Übereinstimmung {
    case Some (Wert) => Wert
    case None => neue MyRuntimeException auslösen ("blah")
  }}
}} 

Und natürlich, wenn Sie Ihren eigenen alternativen Wert für den Fall angeben möchten, dass der Optionist , den NoneSie nur verwenden würden getOrElse:

def foo (): String = {
  val x: Option [String] = Keine
  x.getOrElse ("mein alternativer Wert")
}} 

Natürlich sollte beachtet werden, dass das Abgleichen von Some (Wert), nur um denselben Wert zu extrahieren, keine idiomatische Scala ist, und das getOrElsesollte bevorzugt werden, wenn das alles ist, was man tun möchte. Ich wollte nur den Fall von "falls definiert, auf X auswerten, wenn nicht, Ausnahme auslösen" veranschaulichen.
Sebastian N.

1
Um Guillaume Belrose zu zitieren : "Ich glaube, ich bin dann noch in der Amateurstunde :-)"
Sebastian N.

14
Sie können auch die Vorschläge von @ SebastianN. kombinieren, indem Sie eine Ausnahme getOrElsewie x.getOrElse(throw new MyRuntimeException("message"))
Jona

4
@TravisBrown: Nachdem ich mehr Scala-Erfahrung gesammelt habe, muss ich sagen, dass Sie Recht haben. Ein Abgleich mit dieser Option anstelle von getOrElse ist nicht erforderlich und nicht idiomatisch. Ihre sollte die akzeptierte Antwort sein.
Sebastian N.

132

Eine throw"Anweisung" ist wirklich ein Ausdruck in Scala und hat einen Typ Nothing, der ein Subtyp jedes anderen Typs ist. Dies bedeutet, dass Sie einfach nur alt verwenden können getOrElse:

def myGet[A](oa: Option[A]) = oa.getOrElse(throw new RuntimeException("Can't."))

Das solltest du aber wirklich, wirklich nicht tun.


1
Warum nicht? Was ist eine elegantere / idiomatischere Art, dies zu tun? Mustervergleich?
Sebastian N.

7
Sie erhalten viel mehr Kilometer aus dem Typsystem, wenn Sie Fehler während des gesamten Programms als Werte modellieren (und Ausnahmen für wirklich außergewöhnliche Probleme speichern - z OutOfMemoryError. B. ).
Travis Brown

23
Ich sehe den Vorteil der Modellierungsfehler nicht als Werte für nicht behebbare Probleme (von denen OOME eindeutig eines ist). Nehmen Sie zum Beispiel das Lesen von Metadaten aus einer Konfigurationsdatei. Für die ordnungsgemäße Ausführung des Programms ist eine wohlgeformte Konfigurationsdatei erforderlich. Wenn die Konfigurationsdatei aufgrund eines Benutzerfehlers ungültige Einträge enthält, kann das Programm nicht fortgesetzt werden. Daher erscheint es in solchen Szenarien angemessen, den Stapel abzuwickeln und zurückzusetzen.
Jonathan Neufeld

1
@ JonathanNeufeld Der Kommentar von Travis Brown klingt nach einem eher puristischen Ansatz. Es ist nicht für jeden / jeden Anwendungsfall geeignet. Ich bin mehr auf Ihrer Seite - nur Kaution und fahren Sie nicht zu viel Logik / ausgefallene Systemhandhabung.
StephenBoesch

Ich denke nicht, dass @TravisBrown ein Purist ist, seine anderen Antworten auf SO zeigen etwas anderes. Das Werfen oder Modellieren von Fehlern ist jedoch in der Tat eine Frage des Kontexts. In vielen Fällen ist ein Anwendungsfehler keine Option (jedoch für nicht behebbare Fehler wie OOME). In diesem Fall sollte das Fehlermanagement im gesamten Code modelliert und vom Design her nachvollziehbar sein.
Juh_

14

Verwenden Sie einfach die .get-Methode.

def get[T](o:Option[T]) = o.get

Es wird eine NoSuchElementException ausgelöst, wenn o eine Instanz von None ist.

Grundsätzlich würde ich mit Optionen wie diesen arbeiten:

def addPrint(oi:Option[Int]) = oi.map(_+1).foreach(println)
addPrint(Some(41))
addPrint(Some(1336))
addPrint(None)

um Ihre spezifische Frage zu vermeiden.


4
Ich ging davon aus, dass das OP mehr Kontrolle über die ausgelöste Ausnahme haben wollte, aber selbst wenn nicht, getist dies eine schlechte Idee - es wird nur darüber berichtet, dass Sie etwas Unangenehmes tun. Das Verwenden getOrElseund explizite Auslösen einer Ausnahme ist schwerer zu übersehen, wenn Sie entscheiden, dass dies nicht nur ein einmaliger Code ist und ihn sicherer machen möchte.
Travis Brown

Grundsätzlich scheint es mir natürlich, dass Sie niemals irgendwelche Einstiegsoptionen verwenden sollten, es sei denn, Sie müssen es wirklich wirklich tun. Ich würde foreach oder map verwenden, um Berechnungen mit Optionen durchzuführen.
Felix

11

Ich hoffe, dies hilft Ihnen zu verstehen, wie Fehler (und allgemein Effekte) mithilfe von Typen dargestellt werden.

Fehlerbehandlungsstrategien in der funktionalen Scala

  • Verwenden Sie Optiondiese Option, um optionale Werte zurückzugeben. Beispiel : Entität im Speicher kann nicht gefunden werden.

  • Verwenden Sie Option(possiblyNull)diese Option , um Instanzen von zu vermeiden Some(null).

  • Verwenden Sie Either[Error, T]diese Option , um den erwarteten Fehler zu melden. Beispiel : Das E-Mail-Format ist falsch, es kann keine Zeichenfolge auf eine Zahl analysiert werden usw.

  • Modellieren Sie Ihre Fehler als ADTs (einfach als Typhierarchien bezeichnet ), um sie beispielsweise links von den beiden zu verwenden, um komplexere Fehlerszenarien darzustellen.

  • ExceptionNur werfen , um unerwartete und nicht behebbare Fehler zu signalisieren. Wie fehlende Konfigurationsdatei.

  • Verwenden Sie Either.catchOnlyoder Tryoder Cats.IO(erweitert) anstelle eines Catch-Blocks, um unerwartete Fehler zu behandeln. Hinweis: Sie können ADT weiterhin verwenden , diese jedoch von Wurfobjekten aus erweitern. Mehr über EithervsTry .

  • Verwenden Sie den ValidatedDatentyp aus Cats lib, um Fehler zu akkumulieren, anstatt ausfallsicher zu sein ( Either). Bevorzugen Sie jedoch entweder auf Modulebene, um die Zusammensetzung des Programms zu vereinfachen (um dieselben Typen zu haben). Zum Beispiel - Formulardatenvalidierung, Anhäufung von Analysefehlern.

  • Verwenden Sie die genannten Typen und optimieren Sie das Programm nicht präventiv - da Engpässe höchstwahrscheinlich in der Geschäftslogik und nicht in den Effekttypen enthalten sind.

Ein solcher Ansatz vereinfacht die Wartung und Aktualisierung Ihres Codes, da Sie darüber nachdenken können, ohne auf Implementierungsspezifikationen (auch bekannt als lokales Denken) einzugehen. Außerdem - Fehler reduzieren - können Sie einen Fehler im Typ nicht übersehen. Ausrichten und das Programm leichter (mit Hilfe von map, flatMapund anderen combinators) - da sie auf Typ - Ebene einfacher sind, und nicht mit nicht-lokalen Ausnahmen und Nebenwirkungen.
Weitere Informationen zum Erlernen der funktionalen Scala.


Beachten Sie jedoch, dass sich bei diesem Ansatz manchmal Typen stapeln und es schwieriger werden kann, Dinge zu komponieren. Gegeben zum Beispiel: x: Future[Either[Error, Option[T]]]Was Sie tun können:

  • Verwenden Sie mapund flatMapin Kombination mit dem Mustervergleich, um verschiedene Werte solcher Typen zu erstellen, zum Beispiel:
x.faltMap { case Right(Some(v)) => anotherFuture(v); case Left(er) => ... }
  • Wenn es nicht hilft, können Sie versuchen, MonadTransformers zu verwenden (keine Angst vor dem Namen, es sind nur Wrapper um die Effekttypen wie "Entweder" oder "Zukunft").
  • Eine Möglichkeit besteht auch darin, Ihre ADT-Fehler zu vereinfachen, indem Sie sie aus dem Throwable erweitern, um sie mit Future zu vereinheitlichenFuture[Option[T]]


Und schließlich ist in Ihrem Fall eine Option:

def foo() : Either[Error, String] = {
    val x : Option[String] = ...
    x match {
        case Some(v) => Right(v)
        case None => Left(Error(reason))
    }
}

Ziemlich schöner Überblick. Du hast den letzten "Gedanken"
mitten

@ JordanGeorgiev Link ist faul, entfernt
Albert Bikeev

1

Scala unterstützt diesen Vorgang jetzt auf Karten mithilfe der getOrElse()Methode (siehe Dokumentation hier)

Wie bereits erwähnt, ist das Auslösen einer Ausnahme in Scala ebenfalls ein Ausdruck.

Sie können also Folgendes tun:

myMap.getOrElse(myKey, throw new MyCustomException("Custom Message HERE")
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.