Einführung in die Atlassian Fuge

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 none = Option.none();
assertFalse(none.isDefined());

Option some = Option.some("value");
assertTrue(some.isDefined());
assertEquals("value", some.get());

Option maybe = Option.option(someInputValue);


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 some = Option.some("value")
  .map(x -> null);
assertNull(some.get());


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 optional = Option.none()
  .toOptional();
assertTrue(Option.fromOptional(optional)
  .isEmpty());



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, Option> lifted = Options.lift(f);

assertEquals(2, (long) lifted.apply(Option.some(1)).get());
assertTrue(lifted.apply(Option.none()).isEmpty());

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:

  1. seinen Wert zu extrahieren

  2. Verketten einer Operation mit dem erfolgreichen Wert

  3. 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 recover = Try
  .failure(new Exception("boo!"))
  .recover((Exception e) -> e.getMessage() + " recovered.");

assertTrue(recover.isSuccess());
assertEquals("boo! recovered.", recover.getOrElse(() -> null));


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 failure = Try.failure(new Exception("boo!")).recover(x -> {
    throw new RuntimeException(x);
});

assertTrue(failure.isFailure());


Das Analogon zuflatMap heißtrecoverWith:

Try recover = Try
  .failure(new Exception("boo!"))
  .recoverWith((Exception e) -> Try.successful("recovered again!"));

assertTrue(recover.isSuccess());
assertEquals("recovered again!", recover.getOrElse(() -> null));





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.