Не судите строго, это мой первый моноид

Посмотреть код
public class Main {
    public static void main(String[] args) {
        var results = List.of(
                new GameResult("Petr", 120),
                new GameResult("Ivan", 350),
                new GameResult("Ann", 210),
                new GameResult("Marie", 290)
        );

        var best = Extension.fold(
                results,
                new Max<>(GameResult::new)
        );

        System.out.println(best);
    }
}

interface Monoid<T> {
    T zero();

    T plus(T left, T right);
}

final class Extension {

    public static <T> T fold(
            Collection<T> collection,
            Monoid<T> monoid
    ) {
        return collection.stream()
                .reduce(monoid.zero(), monoid::plus);
    }

}

record GameResult(String player, int score) implements Comparable<GameResult> {

    GameResult() {
        this("", Integer.MIN_VALUE);
    }

    @Override
    public int compareTo(GameResult o) {
        return Integer.compare(score, o.score);
    }
}

final class Max<T extends Comparable<T>> implements Monoid<T> {

    private final Supplier<T> zeroSupplier;

    public Max(Supplier<T> zeroSupplier) {
        this.zeroSupplier = zeroSupplier;
    }

    @Override
    public T zero() {
        return zeroSupplier.get();
    }

    @Override
    public T plus(T left, T right) {
        return left.compareTo(right) > 0
                ? left
                : right;
    }
}

И не пишите, что можно было бы написать это в одну строчку!

var best = results.stream()
        .max(Comparator.naturalOrder())
        .orElse(null);

Моноид здесь, действительно, избыточен. Обычная функциональная цепочка для нахождения максимума читается и пишется намного проще.

Смысл приседания с моноидом в том, что мы универсально описываем, как объект должен вести себя в свёртке, вместо того, чтобы фиксировать алгоритм непосредственно в reduce:

collection.stream()
    .reduce(
        // с чего начинаем / отсутствие результата
        monoid.zero(),
        // как объединить два результата
        monoid::plus
    );

// vs

results.stream()
    .reduce(
        // с чего начинаем / отсутствие результата
        new GameResult(),
        // как объединить два результата
        (left, right) -> left.score() > right.score()
                         ? left
                         : right
        );

Всё ради переиспользуемости кода. Моноид можно поменять, а код внутри reduce останется без изменений, он знает как с ним работать.

Познакомиться подробнее с темой можно здесь.

← Назад к списку статей