Pattern-matching in Java
От: AJ Великобритания  
Дата: 26.04.19 12:06
Оценка:
Коллеги, есть ли какой-то способ упростить следующий код?

import java.util.Arrays;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        Stream<Object> input = Arrays.stream(new Object[] {
            "ABC", 1, Pair.of(3, 4), Pair.of(1, "2"), Pair.of(2, 3), "TEST"
        });
        System.out.println(process(input));
    }
    
    public static int process(Stream<Object> input) {
        
        Predicate<Object> isPair = Pair.class::isInstance;
        Predicate<Pair> isLeftInteger = pair -> pair.left instanceof Integer;
        Predicate<Pair> isRightInteger = pair -> pair.right instanceof Integer;
        
        // Хотелось бы что-то такое как
        // Predicate<Object> isLeftInteger = (Pair) object -> pair.left instanceof Integer;
        // Predicate<Object> isRightInteger = (Pair) object -> pair.right instanceof Integer;
        // чтобы можно было объединить предикаты как
        // input.filter(isPair.and(isLeftInteger).and(isRightInteger))
        
        return input
            .filter(e -> isPair.test(e) && isLeftInteger.test(((Pair) e)) && isRightInteger.test((Pair) e))

            // и вот здесь есть какой-нибудь избежать всей этой цепочки кастов по второму разу?
            .map(e -> ((Integer) ((Pair) e).left + ((Integer) ((Pair) e).right)))

            .mapToInt(Integer::intValue)
            .sum();        
    }

    static class Pair<L, R> {
        
        static <L, R> Pair<L, R> of(L left, R right) {
            Pair<L, R> pair = new Pair<>();
            pair.left = left;
            pair.right = right;
            return pair;
        }
        
        L left;
        R right;
    }
}
Re: Pattern-matching in Java
От: C0s Россия  
Дата: 26.04.19 12:43
Оценка:
Здравствуйте, AJ, Вы писали:

AJ>Коллеги, есть ли какой-то способ упростить следующий код?


пример учебный? (это я к тому, чтобы не писать дурацких вопросов "зачем и кто это сделал так?")

    
AJ>    public static int process(Stream<Object> input) {
        
AJ>        Predicate<Object> isPair = Pair.class::isInstance;
AJ>        Predicate<Pair> isLeftInteger = pair -> pair.left instanceof Integer;
AJ>        Predicate<Pair> isRightInteger = pair -> pair.right instanceof Integer;
        
AJ>        return input
AJ>            .filter(e -> isPair.test(e) && isLeftInteger.test(((Pair) e)) && isRightInteger.test((Pair) e))

AJ>            // и вот здесь есть какой-нибудь избежать всей этой цепочки кастов по второму разу?
AJ>            .map(e -> ((Integer) ((Pair) e).left + ((Integer) ((Pair) e).right)))

AJ>            .mapToInt(Integer::intValue)
AJ>            .sum();

// как-то так, наверное
return input.filter(Pair.class::isInstance).map(Pair.class::cast).filter(isLeftInteger).filter(isRightInteger)
            .mapToInt(p -> ((Integer) p.left) + ((Integer) p.right)).sum();

AJ>    }


потенциально, если облегчить условие суммирования до "всех найденных Integer'ов даже в несимметричных по типу парах", то можно было бы сразу после map(cast) сделать разворот в
flatMap(p -> Stream.concat(p.left, p.right)).filter(Integer.class::isInstance).map(Integer.class::cast).mapToInt(Integer::intValue)
Re[2]: Pattern-matching in Java
От: AJ Великобритания  
Дата: 26.04.19 15:07
Оценка: 9 (1)
Здравствуйте, C0s, Вы писали:

C0s>пример учебный? (это я к тому, чтобы не писать дурацких вопросов "зачем и кто это сделал так?")


Учебный, ага. В реальности я пытаюсь написать плагин к IntelliJ который разбирает дерево из PsiElement'ов и обрабатывает определённые куски кода.

C0s>
    
C0s>// как-то так, наверное
C0s>return input.filter(Pair.class::isInstance).map(Pair.class::cast).filter(isLeftInteger).filter(isRightInteger)
C0s>            .mapToInt(p -> ((Integer) p.left) + ((Integer) p.right)).sum();

AJ>>    }
C0s>


C0s>потенциально, если облегчить условие суммирования до "всех найденных Integer'ов даже в несимметричных по типу парах", то можно было бы сразу после map(cast) сделать разворот в

C0s>
C0s>flatMap(p -> Stream.concat(p.left, p.right)).filter(Integer.class::isInstance).map(Integer.class::cast).mapToInt(Integer::intValue)
C0s>


Спасибо, буду знать про cast. Облегчить условие в реальном случае не выйдет.

Накопал тут либу https://www.vavr.io/ с которой решение выглядит следующим образом

import java.util.Arrays;
import java.util.stream.Stream;

import static io.vavr.API.*;
import static io.vavr.Predicates.*;

public class Main {

    public static void main(String[] args) {
        Stream<Object> input = Arrays.stream(new Object[] {
            "ABC", 1, Pair.of(3, 4), Pair.of(1, "2"), Pair.of(2, 3), "TEST"
        });
        System.out.println(process(input));
    }
    
    public static int process(Stream<Object> input) {
        return input.map(Main::sumIfPairOfInts).mapToInt(Integer::valueOf).sum();
    }
    
    public static Integer sumIfPairOfInts(Object object) {
        return Match(object).of(
                Case(MyPatterns.$Pair($(instanceOf(Integer.class)), $(instanceOf(Integer.class))), (left, right) -> left + right),
                Case($(), () -> 0));
    }
}


Класс ниже процессится их кодо-генератором через Maven
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.match.annotation.Patterns;
import io.vavr.match.annotation.Unapply;

@Patterns
class My {

    @Unapply
    static <L, R> Tuple2<L, R> Pair(Pair<L, R> pair) {
        return Tuple.of(pair.left, pair.right);
    }    
}


На выходе получается вот такая штуковина
// @formatter:off
// CHECKSTYLE:OFF
import io.vavr.API.Match.Pattern;
import io.vavr.API.Match.Pattern2;

// GENERATED BY VAVR <<>> derived from My

@SuppressWarnings("deprecation")
public final class MyPatterns {

    private MyPatterns() {
    }

    public static <L, R, _1 extends L, _2 extends R> Pattern2<Pair<L, R>, _1, _2> $Pair(Pattern<_1, ?> p1, Pattern<_2, ?> p2) {
        return Pattern2.of(Pair.class, p1, p2, My::Pair);
    }

}
// CHECKSTYLE:ON
// @formatter:on


public class Pair<L, R> {
    
    L left;
    R right;
    
    static <L, R> Pair<L, R> of(L left, R right) {
        Pair<L, R> pair = new Pair<>();
        pair.left = left;
        pair.right = right;
        return pair;
    }
}
Отредактировано 26.04.2019 16:34 AJ . Предыдущая версия .
Re: Pattern-matching in Java
От: bzig  
Дата: 27.04.19 23:05
Оценка:
Можно так

public static <T, L, R> Function<T, Pair<L,R>> toPair(Class<L> leftCls, Class<R> rightCls) {
  return object -> object instanceof Pair 
    && ((Pair)object).left != null
    && ((Pair)object).right!= null
    && leftCls.isAssignableFrom(((Pair)object).left.class)
    && rightCls.isAssignableFrom(((Pair)object).right.class)
   ? (Pair<L,R>)object
   : null
}

input
  .map(toPair(Integer.class, Integer.class))
  .filter(Objects::notNull)
  ...


Можно вместо null возвращать Stream<Pair<L,R>> (ну и сигнатуру метода поменять), тогда вызов будет

input
  .flatMap(toPair(Integer.class, Integer.class))
  ...
Re: Pattern-matching in Java
От: vsb Казахстан  
Дата: 28.04.19 08:16
Оценка: +1
Сразу напрашивается сделать после фильтра по типу map который просто прокастует в нужный тип. Тогда всё, что ниже, будет иметь дело с конкретным типом и кастовать не нужно. Если хочется вынести проверку типа и каст в одну операцию, можно использовать flatMap.
Re: Pattern-matching in Java
От: Буравчик Россия  
Дата: 28.04.19 08:59
Оценка:
Здравствуйте, AJ, Вы писали:

AJ>Коллеги, есть ли какой-то способ упростить следующий код?


Так?

public static int process(Stream<Object> input) {

        ...

        return input
            .filter(isPair)
            .map(Pair.class::cast)
            .filter(e -> isLeftInteger.test(e) && isRightInteger.test(e))
            .map(e -> (Pair<Integer,Integer>) e)
            .map(e -> e.left + e.right)
            .mapToInt(Integer::intValue)
            .sum();
Best regards, Буравчик
Re: Pattern-matching in Java
От: · Великобритания  
Дата: 03.05.19 10:57
Оценка:
Здравствуйте, AJ, Вы писали:

AJ>Коллеги, есть ли какой-то способ упростить следующий код?

В лоб решение как-то так:
    int process(final Stream<Object> input) {
        return input

            .filter(Pair.class::isInstance)
            .map(Pair.class::cast)

            .filter(pair -> pair.getLeft() instanceof Integer)
            .filter(pair -> pair.getRight() instanceof Integer)
            .map(pair -> ((Pair<Integer,Integer>) pair))

            .mapToInt(pair -> pair.getLeft() + pair.getRight())
            .sum();
    }

Магию с парой можно вынести в утилитный метод
    static <L, R> Function<Object, Stream<Pair<L, R>>> pairOf(Class<L> l, Class<R> r) {
        return o -> {
            if (!(o instanceof Pair))
                return Stream.empty();
            final Pair p = (Pair) o;
            if( !l.isInstance(p.getLeft()))
                return Stream.empty();
            if( !r.isInstance(p.getRight()))
                return Stream.empty();
            return Stream.of(p);
        };
    }

Тогда можно писать как-то так:
    int process(final Stream<Object> input) {
        return input
            .flatMap(pairOf(Integer.class, Integer.class))
            .mapToInt(pair -> pair.getLeft() + pair.getRight())
            .sum();
    }
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.