Introduction à la fugue d’Atlassian

Introduction à la fugue atlassienne

1. introduction

Fugue est une bibliothèque Java d'Atlassian; c'est un ensemble d'utilitaires prenant en chargeFunctional Programming.

Dans cet article, nous allons nous concentrer et explorer les API les plus importantes de Fugue.

2. Premiers pas avec Fugue

Pour commencer à utiliser la fugue dans nos projets, nous devons ajouter la dépendance suivante:


    io.atlassian.fugue
    fugue
    4.5.1

Nous pouvons trouver la version la plus récente deFugue sur Maven Central.

3. Option

Commençons notre voyage en regardant la classeOption qui est la réponse de Fugue àjava.util.Optional.

Comme on peut le deviner par son nom,Option's a container representing a potentially absent value.

En d'autres termes, unOption est soit la valeurSome d'un certain type, soitNone:

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. L'opérationmap

L'une des API de programmation fonctionnelle standard est la méthodemap() qui permet d'appliquer une fonction fournie aux éléments sous-jacents.

La méthode applique la fonction fournie à la valeur deOption si elle est présente:

Option some = Option.some("value")
  .map(String::toUpperCase);
assertEquals("VALUE", some.get());

3.2. Option et une valeurNull

Outre les différences de dénomination, Atlassian a fait quelques choix de conception pourOption qui diffèrent deOptional; regardons-les maintenant.

We cannot directly create a non-empty Option holding a null value:

Option.some(null);

Ce qui précède lève une exception.

Cependant, nous pouvons en obtenir un en utilisant l'opérationmap():

Option some = Option.some("value")
  .map(x -> null);
assertNull(some.get());


Ce n’est pas possible en utilisant simplementjava.util.Optional.

3.3. Option estIterable

Option peut être traité comme une collection contenant au maximum un élément, il est donc logique qu'il implémente l'interfaceIterable.

Cela augmente considérablement l'interopérabilité lorsque vous travaillez avec des collections / flux.

Et maintenant, par exemple, peut être concaténé avec une autre collection:

Option some = Option.some("value");
Iterable strings = Iterables
  .concat(some, Arrays.asList("a", "b", "c"));

3.4. Conversion deOption enStream

Etant donné qu'unOption est unIterable,, il peut également être facilement converti enStream.

Après la conversion, l'instanceStream aura exactement un élément si l'option est présente, ou zéro sinon:

assertEquals(0, Option.none().toStream().count());
assertEquals(1, Option.some("value").toStream().count());

3.5. Interopérabilitéjava.util.Optional

Si nous avons besoin d'une implémentation standard deOptional, nous pouvons l'obtenir facilement en utilisant la méthodetoOptional():

Optional optional = Option.none()
  .toOptional();
assertTrue(Option.fromOptional(optional)
  .isEmpty());



3.6. La classe d'utilitéOptions

Enfin, Fugue fournit des méthodes utilitaires pour travailler avecOptions dans la classe bien nomméeOptions.

Il comporte des méthodes telles quefilterNone pour supprimer lesOptions vides d'une collection, etflatten pour transformeringa collection deOptions en une collection d'objets fermés, filtrage horsOptions. vides

De plus, il présente plusieurs variantes de la méthodelift qui transforme unFunction<A,B> en unFunction<Option<A>, Option<B>>:

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());

Ceci est utile lorsque nous voulons passer une fonction qui ne connaît pasOption à une méthode qui utiliseOption.

Notez que, tout comme la méthodemap,lift doesn’t map null to None:

assertEquals(null, lifted.apply(Option.some(0)).get());

4. Either pour les calculs avec deux résultats possibles

Comme nous l’avons vu, la classeOption permet de traiter l’absence de valeur de manière fonctionnelle.

Cependant, nous devons parfois renvoyer plus d'informations que «pas de valeur»; Par exemple, nous pourrions vouloir renvoyer une valeur légitime ou un objet d'erreur.

La classeEither couvre ce cas d'utilisation.

Une instance deEither peut être unRight ou unLeft but never both at the same time.

Par convention, la droite est le résultat d'un calcul réussi, tandis que la gauche est le cas exceptionnel.

4.1. Construire unEither

Nous pouvons obtenir une instanceEither en appelant l'une de ses deux méthodes de fabrique statique.

On appelleright si on veut unEither contenant la valeurRight:

Either right = Either.right("value");

Sinon, nous appelonsleft:

Either left = Either.left(-1);

Ici, notre calcul peut renvoyer unString ou unInteger.

4.2. Utilisation d'unEither

Lorsque nous avons une instanceEither, nous pouvons vérifier si elle est à gauche ou à droite et agir en conséquence:

if (either.isRight()) {
    ...
}

Plus intéressant encore, nous pouvons chaîner des opérations en utilisant un style fonctionnel:

either
  .map(String::toUpperCase)
  .getOrNull();

4.3. Projections

La principale chose qui différencie l'un ou l'autre des autres outils monadiques commeOption, Try, est le fait qu'il est souvent impartial. En termes simples, si nous appelons la méthode map (),Either ne sait pas s’il faut travailler avec le côtéLeft ouRight.

C'est ici que les projections sont utiles.

Left and right projections are specular views of an Either that focus on the left or right value, respectivement:

either.left()
  .map(x -> decodeSQLErrorCode(x));

Dans l'extrait de code ci-dessus, siEither estLeft, decodeSQLErrorCode() sera appliqué à l'élément sous-jacent. SiEither estRight,, ce ne sera pas le cas. Idem à l'inverse lorsque vous utilisez la bonne projection.

4.4. Méthodes utilitaires

Comme pourOptions, Fugue fournit également une classe pleine d'utilitaires pourEithers, et il s'appelle exactement comme ça:Eithers.

Il contient des méthodes de filtrage, de transtypage et d'itération sur des collections deEithers.

5. Gestion des exceptions avecTry

Nous concluons notre visite des types de données soit-ceci-soit-cela dans Fugue avec une autre variation appeléeTry.

Try est similaire àEither, mais il diffère en ce qu'il est dédié au travail avec des exceptions.

CommeOption et contrairement àEither,Try est paramétré sur un seul type, car le type «autre» est fixé àException (alors que pourOption, c'est implicitement Void).

Ainsi, unTry peut être unSuccess ou unFailure:

assertTrue(Try.failure(new Exception("Fail!")).isFailure());
assertTrue(Try.successful("OK").isSuccess());

5.1. Instancier unTry

Souvent, nous ne créerons pas unTry explicitement comme un succès ou un échec; nous allons plutôt en créer un à partir d'un appel de méthode.

Checked.of appelle une fonction donnée et renvoie unTry encapsulant sa valeur de retour ou toute exception levée:

assertTrue(Checked.of(() -> "ok").isSuccess());
assertTrue(Checked.of(() -> { throw new Exception("ko"); }).isFailure());

Une autre méthode,Checked.lift, prend une fonction potentiellement lancée etliftsla s vers une fonction renvoyant unTry:

Checked.Function throwException = (String x) -> {
    throw new Exception(x);
};

assertTrue(Checked.lift(throwException).apply("ko").isFailure());

5.2. Travailler avecTry

Une fois que nous avons unTry, les trois choses les plus courantes que nous pourrions vouloir en faire sont:

  1. extraire sa valeur

  2. chaîner une opération à la valeur de succès

  3. gérer l'exception avec une fonction

En outre, évidemment, en supprimant lesTry ou en les passant à d'autres méthodes, les trois ci-dessus ne sont pas les seules options que nous avons, mais toutes les autres méthodes intégrées ne sont qu'une commodité par rapport à ces trois.

5.3. Extraire la valeur réussie

Pour extraire la valeur, nous utilisons la méthodegetOrElse:

assertEquals(42, failedTry.getOrElse(() -> 42));

Elle renvoie la valeur réussie si présente ou une valeur calculée sinon.

Il n'y a pas degetOrThrow ou similaire, mais commegetOrElse ne détecte aucune exception, nous pouvons facilement l'écrire:

someTry.getOrElse(() -> {
    throw new NoSuchElementException("Nothing to get");
});

5.4. Chaînage des appels après un succès

Dans un style fonctionnel, nous pouvons appliquer une fonction à la valeur de succès (si présente) sans l'extraire explicitement.

C'est la méthode typiquemap que nous trouvons dansOption,Either et la plupart des autres conteneurs et collections:

Try aTry = Try.successful(42).map(x -> x + 1);

Il renvoie unTry afin que nous puissions enchaîner d'autres opérations.

Bien sûr, nous avons aussi la variétéflatMap:

Try.successful(42).flatMap(x -> Try.successful(x + 1));

5.5. Récupération des exceptions

Nous avons des opérations de mappage analogues qui fonctionnent à l'exception d'unTry (si présent), plutôt que de sa valeur réussie.

Cependant, ces méthodes diffèrent en ce que leur signification estto recover from the exception, i.e. to produce a successful Try dans le cas par défaut.

Ainsi, nous pouvons produire une nouvelle valeur avecrecover:

Try recover = Try
  .failure(new Exception("boo!"))
  .recover((Exception e) -> e.getMessage() + " recovered.");

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


Comme on peut le constater, la fonction de récupération prend l'exception comme seul argument.

Si la fonction de récupération elle-même lance, le résultat est un autreTry échoué:

Try failure = Try.failure(new Exception("boo!")).recover(x -> {
    throw new RuntimeException(x);
});

assertTrue(failure.isFailure());


L'analogue àflatMap est appelérecoverWith:

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

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





6. Autres utilitaires

Jetons maintenant un coup d'œil à certains des autres utilitaires de Fugue, avant de terminer.

6.1. Paires

UnPair est une structure de données vraiment simple et polyvalente, composée de deux composants tout aussi importants, que Fugue appelleleft etright:

Pair pair = Pair.pair(1, "a");

assertEquals(1, (int) pair.left());
assertEquals("a", pair.right());

Fugue ne fournit pas beaucoup de méthodes intégrées surPairs, à part le mappage et le modèle de foncteur applicatif.

Cependant, lesPairs sont utilisés dans toute la bibliothèque et sont facilement disponibles pour les programmes utilisateur.

La prochaine implémentation de Lisp par le pauvre n'est qu'à quelques touches!

6.2. Unit

Unit est une énumération avec une valeur unique qui est censée représenter «aucune valeur».

Il remplace le type de retour void et la classeVoid, qui supprimenull:

Unit doSomething() {
    System.out.println("Hello! Side effect");
    return Unit();
}

De manière assez surprenante, cependant,Option doesn’t understand Unit, treating it like some value instead of none.

6.3. Utilitaires statiques

Nous avons quelques classes remplies de méthodes utilitaires statiques que nous n’aurons pas à écrire ni à tester.

La classeFunctions propose des méthodes qui utilisent et transforment les fonctions de différentes manières: composition, application, currying, fonctions partielles utilisantOption, mémorisation faible, etc.

La classeSuppliers fournit une collection similaire, mais plus limitée, d'utilitaires pourSuppliers, c'est-à-dire des fonctions sans arguments.

Iterables etIterators, enfin, contiennent une foule de méthodes statiques pour manipuler ces deux interfaces Java standard largement utilisées.

7. Conclusion

Dans cet article, nous avons présenté un aperçu de la bibliothèque Fugue d'Atlassian.

Nous n’avons pas touché aux classes algébriques telles queMonoid etSemigroups car elles ne rentrent pas dans un article généraliste.

Cependant, vous pouvez en savoir plus sur eux et plus encore dans les Fuguejavadocs etsource code.

Nous n'avons également abordé aucun des modules optionnels, qui offrent par exemple des intégrations avec Guava et Scala.

L'implémentation de tous ces exemples et extraits de code se trouve dansthe GitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.