Comment composer une fonction et un consommateur en Java 8?


Pourquoi ne pouvons-nous pas composer une fonction et un consommateur comme nous composons des fonctions?

Function<Integer, String> a = Object::toString;
Consumer<String> b = x -> System.out.println(x);
Consumer<Integer> composed = a.andThen(b);

Cela semble être une improvisation évidente à l'interface de fonction existante. Y a-t-il une raison pour laquelle cette capacité a été évitée dans Java 8?

De plus, est-ce une pratique courante d'utiliser une implémentation étendue de la fonction comme suit?

interface FunctionImproved<T, R> extends Function<T, R> {
    default Consumer<T> andThen(Consumer<R> consumer) {
        return x -> consumer.accept(apply(x));
    }
}
Author: Andrew Tobilko, 2017-11-28

4 answers

Le andThen méthode sur un Function<A0, A1> exemple s'attend à un Function<A1, A2>, pas Consumer<A1>:

a.andThen(string -> {
    b.accept(string);
    return string;
});

Cela a un effet secondaire puisque notre "consommateur" (en fait, c'est un Function<A1, A1>) retournera une valeur ignorée de la fonction Function<Integer, String> a précédente. Ce n'est pas ce que nous voulons.


Il existe une façon différente de composer un Function<A0, A1> et un Consumer<A1>:

Consumer<Integer> c = i -> b.accept(a.apply(i));

Qui peut être généralisé à une méthode d'utilité:

<A0, A1> Consumer<A0> functionAndThenConsumer(Function<A0, A1> f, Consumer<A1> c) {
    return i -> c.accept(f.apply(i));
}

Est-ce une pratique courante d'utiliser une implémentation étendue de Function?

Il y a deux options:

  1. Avoir des méthodes statiques qui font des conversions entre les interfaces fonctionnelles (comme je l'ai fait).

  2. Extension de ces interfaces fonctionnelles standard avec des méthodes par défaut (comme vous l'avez fait). Il suffit de choisir un bon nom significatif, pas comme FunctionImproved.

 2
Author: Andrew Tobilko, 2017-11-28 00:12:15

Functions et Consumers servir deux maîtres (dans un sens).

  • Une fonction accepte un (ou deux dans le cas de BiFunction) types et produit un troisième type, qui peut être le premier ou le deuxième type.
  • Un consommateur accepte un (ou deux dans le cas de BiConsumer) types et produit non sortie.

Dans les deux cas, il devrait y avoir une opération discrète et unique en cours, ce qui faciliterait la raison sécurité du filetage de l'opération.

Il ressemble à comme si vous essayiez de faire un mappage simple d'un type à un autre (Chaîne en entier), puis appelez une opération dessus. Si vos éléments étaient dans un Stream, cela pourrait être écrit comme:

elements.map(Objects::toString).forEach(System.out::println);

...et si c'était dans une collection ou IntStream, vous pouvez l'utiliser à la place:

elements.forEach(System.out::println);

En fin de compte, ce que vous essayez peut être une solution à la recherche d'un problème. Une fois que vous êtes plus clair quant au rôle qui fonctionne et les consommateurs en fait jouer, les tentatives de compositions comme ci-dessus deviennent moins nécessaires.

 1
Author: Makoto, 2017-11-27 21:45:12

Pourquoi ne pouvons-nous pas composer une fonction et un consommateur comme nous composons des fonctions?

Tout est à propos du système de type Java. La méthode Function.andThen accepte un argument de type Function (qui n'est pas de type Consumer). Il n'y a pas de relation entre un Consumer et un Function en Java, c'est-à-dire que l'un n'étend pas l'autre, donc vous ne pouvez pas passer un Consumer à la méthode andThen de Function.

Il existe cependant des solutions de contournement disponibles. L'un est avec une méthode par défaut, comme vous l'avez montré, tandis qu'une autre approche serait d'utiliser une méthode statique, comme le montre Andrew réponse.

Voici une autre façon, en utilisant une fonction d'ordre supérieur:

BiFunction<Function<T, R>, Consumer<R>, Consumer<T>> composer =
    (function, consumer) -> t -> consumer.accept(function.apply(t));

Vous pouvez l'utiliser de cette façon:

Consumer<Integer> composed = composer.apply(Integer::toString, System.out::println);
 1
Author: Federico Peralta Schaffner, 2017-11-27 22:35:44

Il n'y a aucune raison, pourquoi cette composition ne devrait pas exister dans le cadre de l'API de fonction. Les concepteurs de Java 8 n'ont tout simplement pas prévu toutes les combinaisons possibles et les cas d'utilisation: c'est pourquoi Java 9 a ajouté des méthodes plus pratiques et nous en verrons probablement plus dans les prochaines versions.

En ce qui concerne la fonction "améliorée", je n'oserais pas créer une nouvelle interface juste pour cela, surtout compte tenu du choix du nom de l'interface. Le suffixe "Amélioré", tout comme tous les " Utils", "Helpers", etc., ne possède pas de sémantique auto-explicative et doit être évité. La meilleure solution consiste à créer un espace de noms fonctionnel comme celui-ci:

final class Compositions {
     private Compositions() {}
     public static <T,R> Function<Consumer<R>, Consumer<T>> to(Function<T,R> f) { 
          return consumer -> (value -> consumer.accept(f.apply(value));
     } 
}

import static Compositions.*;
Function<Integer,String> stringValue = Integer::toString;
Consumer<Integer> printInt = to(stringValue).apply(System.out::println);
printInt.accept(1);
 -1
Author: Ivan Gammel, 2017-11-27 22:31:33