web-dev-qa-db-fra.com

Java 8 Pattern Matching?

Java 8 prendra-t-il en charge le filtrage par motif comme Scala et d’autres programmes fonctionnels? Je prépare une présentation des fonctionnalités Lambda de Java 8. Je ne trouve rien sur ce concept de programmation fonctionnelle particulier.

Je me souviens de ce qui m'intéressait à la programmation fonctionnelle était la mise en œuvre du tri rapide, surtout par rapport à la mise en œuvre de la programmation impérative.

28
edarroyo

Je suppose que vous ne parlez pas de correspondance de motif dans le sens où vous appliquez une expression régulière à une chaîne, mais comme appliqué dans Haskell . Par exemple en utilisant des jokers:

head (x:_)  = x
tail (_:xs) = xs

Java 8 ne supportera pas cela de manière native, mais avec l'expression Lambda, il y a cependant plusieurs façons de le faire, comme ceci pour calculer la factorielle:

public static int fact(int n) {
     return ((Integer) new PatternMatching(
          inCaseOf(0, _ -> 1),
          otherwise(  _ -> n * fact(n - 1))
     ).matchFor(n));
}

Comment implémenter cela, vous trouverez plus d’informations dans cet article de blog: Vers une correspondance des modèles en Java .

30
Konrad Reiche

Il est possible d'implémenter le filtrage par motif sous forme de bibliothèque dans Java 8 (en tirant parti des expressions lambda), mais malheureusement, le contrôle d'exhaustivité du compilateur que des langages tels que Haskell ou Scala ont encore fait défaut.

Cyclops-react a un puissant/ Pattern Matching module, qui offre à la fois une correspondance de structure pour Java 8 et une correspondance de configuration via des gardes.

Il fournit un rapport DSL quand/alors/sinon, et la correspondance, y compris la déconstruction, est basée sur les prédicats Java standard (la correspondance peut donc être utilisée pour filtrer un flux, par exemple).

Matching par les gardes

Pour l'appariement via des gardes, nous utilisons whenGuard/then/sinon pour montrer clairement que le cas pilote le test et non la structure de l'objet à tester.

par exemple. Pour la correspondance basée sur la protection, si nous implémentons une classe Case qui implémente l'interface Matchable

 static class MyCase  implements Matchable{ int a; int b; int c;}

(au fait, Lombok peut être très utile pour créer des hiérarchies de classes d’affaires scellées)

Nous pouvons apparier ses valeurs internes (récursivement si nécessaire, ou par type parmi diverses autres options).

  import static com.aol.cyclops.control.Matchable.otherwise;
  import static com.aol.cyclops.control.Matchable.whenGuard;

  new MyCase(1,2,3).matches(c->c.is(whenGuard(1,2,3)).then("hello"),
                               .is(whenGuard(4,5,6)).then("goodbye")
                               ,otherwise("goodbye")
                           );

Si nous avons un objet qui n'implémente pas [Matchable] [3], nous pouvons le contraindre à Matchable de toute façon, notre code deviendrait 

Matchable.ofDecomposable(()->new MyCase(1,2,3)))
         .matches(c->c.is(whenGuard(1,2,3)).then("hello"),
                      .is(whenGuard(4,5,6)).then("goodbye")
                      ,otherwise("hello"));

Si nous ne nous soucions pas de l'une des valeurs, nous pouvons utiliser des caractères génériques 

new MyCase(1,2,3).matches(c->c.is(whenGuard(1,__,3)).then("hello"),
                              .is(whenGuard(4,__,6)).then("goodbye")
                              ,otherwise("hello)
                           );

Ou déstructurer de manière récursive un ensemble imbriqué de classes

Matchable.of(new NestedCase(1,2,new NestedCase(3,4,null)))
                .matches(c->c.is(whenGuard(1,__,has(3,4,__)).then("2")
                 ,otherwise("default");

Où NestedCase ressemble à quelque chose comme ça -

class NestedCase implemends Decomposable { int a; int b; NestedCase c; }

Les utilisateurs peuvent également composer des expressions de correspondance de modèle à l'aide de hamcrest.

 import static com.aol.cyclops.control.Matchable.otherwise;
 import static com.aol.cyclops.control.Matchable.then;
 import static com.aol.cyclops.control.Matchable.when;

 Matchable.of(Arrays.asList(1,2,3))
                .matches(c->c.is(when(equalTo(1),any(Integer.class),equalTo(4)))
                        .then("2"),otherwise("default"));

Correspondance structurelle

Nous pouvons également faire correspondre la structure exacte de l'objet à tester. C'est plutôt que d'utiliser si/alors des tests pour voir si la structure correspond à nos cas, nous pouvons demander au compilateur de s'assurer que nos cas correspondent à la structure des objets fournis. Pour ce faire, le DSL est presque identique selon la comparaison basée sur la garde, mais nous utilisons quand/alors/sinon pour montrer clairement la structure Objects conduit les scénarios de test et non l'inverse.

  import static com.aol.cyclops.control.Matchable.otherwise;
  import static com.aol.cyclops.control.Matchable.then;
  import static com.aol.cyclops.control.Matchable.when;

  String result =  new Customer("test",new Address(10,"hello","my city"))
                            .match()
                            .on$_2()
                            .matches(c->c.is(when(decons(when(10,"hello","my city"))),then("hello")), otherwise("miss")).get();

  //"hello"

Correspondance structurelle sur un objet d'adresse extrait d'un client. Où les classes Client et Adresse ont ceci

@AllArgsConstructor
static class Address{
    int house;
    String street;
    String city;

    public MTuple3<Integer,String,String> match(){
        return Matchable.from(()->house,()->street,()->city);
    }
}
@AllArgsConstructor
static class Customer{
    String name;
    Address address;
    public MTuple2<String,MTuple3<Integer,String,String>> match(){
        return Matchable.from(()->name,()->Maybe.ofNullable(address).map(a->a.match()).orElseGet(()->null));
    }
}

cyclops-react fournit un Matchables class qui permet la correspondance de modèle structurel avec les types JDK courants.

9
John McClean

Je suis conscient que cette question a déjà été répondue. De plus, je suis novice en programmation fonctionnelle mais, après beaucoup d'hésitation, j'ai finalement décidé de m'investir dans cette discussion pour avoir des retours sur ce qui suit.

Je suggérerais la (trop?) Simple implémentation ci-dessous. Il diffère légèrement de l'article (Nice) cité dans la réponse acceptée; mais dans ma (courte) expérience, il était un peu plus souple à utiliser et facile à entretenir (ce qui est bien sûr aussi une question de goût).

import Java.util.Optional;
import Java.util.function.Function;
import Java.util.function.Predicate;

final class Test
{
    public static final Function<Integer, Integer> fact = new Match<Integer>()
            .caseOf( i -> i == 0, i -> 1 )
            .otherwise( i -> i * Test.fact.apply(i - 1) );

    public static final Function<Object, String> dummy = new Match<Object>()
            .caseOf( i -> i.equals(42), i -> "forty-two" )
            .caseOf( i -> i instanceof Integer, i -> "Integer : " + i.toString() )
            .caseOf( i -> i.equals("world"), i -> "Hello " + i.toString() )
            .otherwise( i -> "got this : " + i.toString() );

    public static void main(String[] args)
    {
        System.out.println("factorial : " + fact.apply(6));
        System.out.println("dummy : " + dummy.apply(42));
        System.out.println("dummy : " + dummy.apply(6));
        System.out.println("dummy : " + dummy.apply("world"));
        System.out.println("dummy : " + dummy.apply("does not match"));
    }
}

final class Match<T>
{
    public <U> CaseOf<U> caseOf(Predicate<T> cond, Function<T, U> map)
    {
        return this.new CaseOf<U>(cond, map, Optional.empty());
    }

    class CaseOf<U> implements Function<T, Optional<U>>
    {
        private Predicate<T> cond;
        private Function<T, U> map;
        private Optional<CaseOf<U>> previous;

        CaseOf(Predicate<T> cond, Function<T, U> map, Optional<CaseOf<U>> previous)
        {
          this.cond = cond;
          this.map = map;
          this.previous = previous;
        }

        @Override
        public Optional<U> apply(T value)
        {
            Optional<U> r = previous.flatMap( p -> p.apply(value) );
            return r.isPresent() || !cond.test(value) ? r
                : Optional.of( this.map.apply(value) );
        }

        public CaseOf<U> caseOf(Predicate<T> cond, Function<T, U> map)
        {
          return new CaseOf<U>(cond, map, Optional.of(this));
        }

        public Function<T,U> otherwise(Function<T, U> map)
        {
            return value -> this.apply(value)
                .orElseGet( () -> map.apply(value) );
        }
    }
}
2
user7272806

Derive4J est une bibliothèque visant à prendre en charge une prise en charge quasi native des types de somme et des correspondances de modèles structurels pour Java (et même plus). En prenant une petite calculatrice DSL par exemple, avec Derive4J, vous pouvez écrivez le code suivant:

import Java.util.function.Function;
import org.derive4j.Data;
import static org.derive4j.exemple.Expressions.*;

@Data
public abstract class Expression {

    interface Cases<R> {
        R Const(Integer value);
        R Add(Expression left, Expression right);
        R Mult(Expression left, Expression right);
        R Neg(Expression expr);
    }

    public abstract <R> R match(Cases<R> cases);

    private static Function<Expression, Integer> eval = Expressions
        .match()
            .Const(value        -> value)
            .Add((left, right)  -> eval(left) + eval(right))
            .Mult((left, right) -> eval(left) * eval(right))
            .Neg(expr           -> -eval(expr));

    public static Integer eval(Expression expression) {
        return eval.apply(expression);
    }

    public static void main(String[] args) {
        Expression expr = Add(Const(1), Mult(Const(2), Mult(Const(3), Const(3))));
        System.out.println(eval(expr)); // (1+(2*(3*3))) = 19
    }
}
0
JbGi