Einführung in die Atlassische Fuge
1. Einführung
Fugue ist eine Java-Bibliothek von Atlassian; Es handelt sich um eine Sammlung von Dienstprogrammen, dieFunctional Programming unterstützen.
In diesem Artikel konzentrieren wir uns auf die wichtigsten APIs der Fuge und untersuchen sie.
2. Erste Schritte mit der Fuge
Um Fugue in unseren Projekten zu verwenden, müssen wir die folgende Abhängigkeit hinzufügen:
io.atlassian.fugue
fugue
4.5.1
Wir können die neueste Version vonFugue auf Maven Central finden.
3. Option
Beginnen wir unsere Reise mit einem Blick auf die KlasseOption, die die Antwort der Fuge aufjava.util.Optional.ist
Wie wir anhand des Namens erraten können,Option's a container representing a potentially absent value.
Mit anderen Worten, einOption ist entwederSome Wert eines bestimmten Typs oderNone:
Option
3.1. Diemap Operation
Eine der Standard-APIs für die funktionale Programmierung ist diemap()-Methode, mit der eine bereitgestellte Funktion auf zugrunde liegende Elemente angewendet werden kann.
Die Methode wendet die bereitgestellte Funktion auf den Wert vonOptionan, wenn dieser vorhanden ist:
Option some = Option.some("value")
.map(String::toUpperCase);
assertEquals("VALUE", some.get());
3.2. Option und aNull Wert
Neben den Namensunterschieden hat Atlassian einige Designentscheidungen fürOption getroffen, die sich vonOptional unterscheiden. Schauen wir sie uns jetzt an.
We cannot directly create a non-empty Option holding a null value:
Option.some(null);
Das obige löst eine Ausnahme aus.
Wir können jedoch eine erhalten, wenn wir die Operationmap()verwenden:
Option
Dies ist nicht möglich, wenn Sie einfachjava.util.Optional. verwenden
3.3. Option istIterable
Option kann als Sammlung behandelt werden, die maximal ein Element enthält. Daher ist es sinnvoll, dieIterable-Schnittstelle zu implementieren.
Dies erhöht die Interoperabilität beim Arbeiten mit Sammlungen / Streams erheblich.
Und jetzt kann zum Beispiel mit einer anderen Sammlung verkettet werden:
Option some = Option.some("value");
Iterable strings = Iterables
.concat(some, Arrays.asList("a", "b", "c"));
3.4. Konvertieren vonOption inStream
Da einOption einIterable, ist, kann es auch leicht in einStream umgewandelt werden.
Nach der Konvertierung hat die Instanz vonStreamgenau ein Element, wenn die Option vorhanden ist, oder andernfalls Null:
assertEquals(0, Option.none().toStream().count());
assertEquals(1, Option.some("value").toStream().count());
3.5. java.util.Optional Interoperabilität
Wenn wir eine Standardimplementierung vonOptionalbenötigen, können wir diese einfach mit der MethodetoOptional()erhalten:
Optional
3.6. Die Utility-KlasseOptions
Schließlich bietet Fugue einige Dienstprogrammmethoden für die Arbeit mitOptions in der treffend benanntenOptions-Klasse.
Es enthält Methoden wiefilterNone zum Entfernen leererOptions aus einer Sammlung undflatten zum Verwandeln voningals Sammlung vonOptions in eine Sammlung eingeschlossener Objekte, wobei gefiltert wird out leerOptions.
Darüber hinaus bietet es verschiedene Varianten der Methodelift, mit derFunction<A,B> inFunction<Option<A>, Option<B>> umgewandelt werden:
Function f = (Integer x) -> x > 0 ? x + 1 : null;
Function
Dies ist nützlich, wenn wir eine Funktion, dieOption nicht kennt, an eine Methode übergeben möchten, dieOption verwendet.
Beachten Sie, dasslift doesn’t map null to None: genau wie die Methodemap
assertEquals(null, lifted.apply(Option.some(0)).get());
4. Either für Berechnungen mit zwei möglichen Ergebnissen
Wie wir gesehen haben, können wir mit der KlasseOptiondas Fehlen eines Werts auf funktionale Weise behandeln.
Manchmal müssen wir jedoch mehr Informationen als "kein Wert" zurückgeben. Beispielsweise möchten wir möglicherweise entweder einen legitimen Wert oder ein Fehlerobjekt zurückgeben.
Die KlasseEitherdeckt diesen Anwendungsfall ab.
Eine Instanz vonEither kannRight oderLeft but never both at the same time sein.
Konventionell ist das Recht das Ergebnis einer erfolgreichen Berechnung, während das Linke der Ausnahmefall ist.
4.1. Konstruieren einesEither
Wir können eineEither-Instanz erhalten, indem wir eine der beiden statischen Factory-Methoden aufrufen.
Wir nennenright, wenn wir wollen, dassEither denRight-Wert: enthält
Either right = Either.right("value");
Andernfalls rufen wirleft auf:
Either left = Either.left(-1);
Hier kann unsere Berechnung entweder einString oder einInteger. zurückgeben
4.2. Mit einemEither
Wenn wir eineEither-Instanz haben, können wir prüfen, ob sie links oder rechts ist, und entsprechend handeln:
if (either.isRight()) {
...
}
Interessanterweise können wir Operationen mit einem funktionalen Stil verketten:
either
.map(String::toUpperCase)
.getOrNull();
4.3. Projektionen
Die Hauptsache, die sich von anderen monadischen Tools wieOption, Try,unterscheidet, ist die Tatsache, dass es oft unvoreingenommen ist. Einfach ausgedrückt, wenn wir die map () -Methode aufrufen, weißEither nicht, ob er mitLeft oderRightarbeiten soll.
Hier bieten sich Projektionen an.
Left and right projections are specular views of an Either that focus on the left or right value:
either.left()
.map(x -> decodeSQLErrorCode(x));
Wenn im obigen CodefragmentEitherLeft, decodeSQLErrorCode() ist, wird es auf das zugrunde liegende Element angewendet. WennEitherRight, ist, wird dies nicht der Fall sein. Dasselbe gilt umgekehrt, wenn Sie die richtige Projektion verwenden.
4.4. Dienstprogrammmethoden
Wie beiOptions bietet Fugue auch fürEithers eine Klasse voller Dienstprogramme, die einfach so heißt:Eithers.
Es enthält Methoden zum Filtern, Umwandeln und Iterieren von Sammlungen vonEithers.
5. Ausnahmebehandlung mitTry
Wir schließen unsere Tour durch diese oder jene Datentypen in Fuge mit einer anderen Variation namensTry ab.
Try ähneltEither, unterscheidet sich jedoch darin, dass es für die Arbeit mit Ausnahmen vorgesehen ist.
WieOption und anders alsEither wirdTry über einen einzelnen Typ parametrisiert, da der „andere“ Typ aufException festgelegt ist (während er fürOption implizit ist Void).
EinTry kann also entweder einSuccess oder einFailure sein:
assertTrue(Try.failure(new Exception("Fail!")).isFailure());
assertTrue(Try.successful("OK").isSuccess());
5.1. Instanziieren vonTry
Oft erstellen wir einTrynicht explizit als Erfolg oder Misserfolg. Stattdessen erstellen wir eine aus einem Methodenaufruf.
Checked.of ruft eine bestimmte Funktion auf und gibt einTry zurück, das den Rückgabewert oder eine ausgelöste Ausnahme kapselt:
assertTrue(Checked.of(() -> "ok").isSuccess());
assertTrue(Checked.of(() -> { throw new Exception("ko"); }).isFailure());
Eine andere Methode,Checked.lift, nimmt eine potenziell werfende Funktion undlifts sie zu einer Funktion, dieTry zurückgibt:
Checked.Function throwException = (String x) -> {
throw new Exception(x);
};
assertTrue(Checked.lift(throwException).apply("ko").isFailure());
5.2. Arbeiten mitTry
Sobald wir einTry haben, sind die drei häufigsten Dinge, die wir letztendlich damit machen möchten:
-
seinen Wert zu extrahieren
-
Verketten einer Operation mit dem erfolgreichen Wert
-
Behandlung der Ausnahme mit einer Funktion
Abgesehen davon, dass dieTry verworfen oder an andere Methoden weitergegeben werden, sind die oben genannten drei nicht die einzigen Optionen, die wir haben, aber alle anderen integrierten Methoden sind nur eine Annehmlichkeit gegenüber diesen drei.
5.3. Den erfolgreichen Wert extrahieren
Um den Wert zu extrahieren, verwenden wir die MethodegetOrElse:
assertEquals(42, failedTry.getOrElse(() -> 42));
Gibt den erfolgreichen Wert zurück, falls vorhanden, oder einen anderen berechneten Wert.
Es gibt keinegetOrThrow oder ähnliches, aber dagetOrElse keine Ausnahme abfängt, können wir es einfach schreiben:
someTry.getOrElse(() -> {
throw new NoSuchElementException("Nothing to get");
});
5.4. Verketten von Anrufen nach Erfolg
In einem funktionalen Stil können wir eine Funktion auf den Erfolgswert (falls vorhanden) anwenden, ohne ihn zuerst explizit zu extrahieren.
Dies ist die typischemap-Methode, die wir inOption,Either und den meisten anderen Containern und Sammlungen finden:
Try aTry = Try.successful(42).map(x -> x + 1);
Es gibt einTry zurück, damit wir weitere Operationen verketten können.
Natürlich haben wir auch die SorteflatMap:
Try.successful(42).flatMap(x -> Try.successful(x + 1));
5.5. Wiederherstellung nach Ausnahmen
Wir haben analoge Mapping-Operationen, die mit Ausnahme vonTry (falls vorhanden) und nicht mit dessen erfolgreichem Wert funktionieren.
Diese Methoden unterscheiden sich jedoch darin, dass ihre Bedeutung im Standardfallto recover from the exception, i.e. to produce a successful Try ist.
Somit können wir mitrecover einen neuen Wert erzeugen:
Try
Wie wir sehen können, nimmt die Wiederherstellungsfunktion die Ausnahme als einziges Argument.
Wenn die Wiederherstellungsfunktion selbst ausgelöst wird, ist das Ergebnis ein weiterer fehlgeschlagenerTry:
Try
Das Analogon zuflatMap heißtrecoverWith:
Try
6. Andere Dienstprogramme
Lassen Sie uns nun einen kurzen Blick auf einige der anderen Dienstprogramme in Fugue werfen, bevor wir sie abschließen.
6.1. Paare
APair ist eine wirklich einfache und vielseitige Datenstruktur, die aus zwei gleich wichtigen Komponenten besteht, die Fugeleft undright nennt:
Pair pair = Pair.pair(1, "a");
assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());
Die Fuge bietet neben dem Mapping und dem anwendbaren Funktormuster nicht viele integrierte Methoden fürPairs.
Pairs werden jedoch in der gesamten Bibliothek verwendet und sind für Benutzerprogramme leicht verfügbar.
Die Implementierung von Lisp durch die nächste arme Person ist nur ein paar Tastenanschläge entfernt!
6.2. Unit
Unit ist eine Aufzählung mit einem einzelnen Wert, die "kein Wert" darstellen soll.
Es ist ein Ersatz für den Rückgabetyp void und die KlasseVoid, dernull beseitigt:
Unit doSomething() {
System.out.println("Hello! Side effect");
return Unit();
}
Überraschenderweise jedochOption doesn’t understand Unit, treating it like some value instead of none.
6.3. Statische Dienstprogramme
Wir haben einige Klassen voller statischer Dienstprogrammmethoden, die wir nicht schreiben und testen müssen.
Die KlasseFunctions bietet Methoden, die Funktionen auf verschiedene Arten verwenden und transformieren: Zusammensetzung, Anwendung, Currying, Teilfunktionen mitOption, schwache Memoisierung usw.
Die KlasseSuppliers bietet eine ähnliche, aber eingeschränktere Sammlung von Dienstprogrammen fürSuppliers, dh Funktionen ohne Argumente.
Iterables undIterators enthalten schließlich eine Vielzahl statischer Methoden zur Bearbeitung dieser beiden weit verbreiteten Standard-Java-Schnittstellen.
7. Fazit
In diesem Artikel haben wir einen Überblick über die Fugenbibliothek von Atlassian gegeben.
Wir haben die algebralastigen Klassen wieMonoid undSemigroups nicht berührt, weil sie nicht in einen allgemeinen Artikel passen.
Sie können jedoch darüber und mehr in den Fugenjavadocs undsource code lesen.
Wir haben auch keines der optionalen Module angesprochen, die beispielsweise Integrationen mit Guava und Scala bieten.
Die Implementierung all dieser Beispiele und Codefragmente finden Sie inthe GitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein.