Каковы веские причины для выбора инвариантности в API, таком как Stream.reduce()?
Обзор Java 8 Stream
API дизайн, я был удивлен общей неизменности на Stream.reduce()
аргументы:
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
Казалось бы, более универсальная версия того же API могла бы применять ковариацию / контравариантность к отдельным ссылкам на U
, такие как:
<U> U reduce(U identity,
BiFunction<? super U, ? super T, ? extends U> accumulator,
BiFunction<? super U, ? super U, ? extends U> combiner)
Это позволило бы следующее, что в настоящее время невозможно:
// Assuming we want to reuse these tools all over the place:
BiFunction<Number, Number, Double> numberAdder =
(t, u) -> t.doubleValue() + u.doubleValue();
// This currently doesn't work, but would work with the suggestion
Stream<Number> stream = Stream.of(1, 2L, 3.0);
double sum = stream.reduce(0.0, numberAdder, numberAdder);
Обходной путь, используйте ссылки на методы, чтобы "привести" типы в целевой тип:
double sum = stream.reduce(0.0, numberAdder::apply, numberAdder::apply);
C# не имеет этой конкретной проблемы, так как Func(T1, T2, TResult)
определяется следующим образом, используя объявление-сайт дисперсии, что означает, что любой API, использующий Func
получает это поведение бесплатно:
public delegate TResult Func<in T1, in T2, out TResult>(
T1 arg1,
T2 arg2
)
Каковы преимущества (и, возможно, причины принятия решений ЭГ) существующего проекта по сравнению с предлагаемым проектом?
Или, по-другому, спросить, какие предостережения в предлагаемом проекте, которые я мог бы не заметить (например, трудности вывода типов, ограничения распараллеливания или ограничения, характерные для операции сокращения, такие как, например, ассоциативность, ожидание будущей дисперсии сайта объявления Java на BiFunction<in T, in U, out R>
...)?
2 ответа
Просматривать историю развития лямбды и выявлять причину этого решения сложно, поэтому в конечном итоге придется ждать, пока один из разработчиков ответит на этот вопрос.
Некоторые подсказки могут быть следующими:
Интерфейсы потока прошли несколько итераций и рефакторингов. В одной из самых ранних версий
Stream
интерфейс, там были выделеныreduce
методы, и тот, который ближе всего кreduce
метод в вопросе все еще называлсяStream#fold
тогда Этот уже получилBinaryOperator
какcombiner
параметр.Интересно, что довольно долгое время, предложение лямбда включало выделенный интерфейс
Combiner<T,U,R>
, Противоположно, это не использовалось какcombiner
вStream#reduce
функция. Вместо этого он использовался какreducer
что, по-видимому, в настоящее время называетсяaccumulator
, Тем не менееCombiner
интерфейс был заменен наBiFunction
в более поздней редакции.Самое поразительное сходство с вопросом здесь можно найти в теме о
Stream#flatMap
подпись в списке рассылки, который затем превращается в общий вопрос о дисперсиях подписей потокового метода. Они исправили это в некоторых местах, напримерКак Брайан поправил меня:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
вместо:
<R> Stream<R> flatMap(Function<T, Stream<? extends R>> mapper);
Но заметил, что в некоторых местах это было невозможно:
T reduce(T identity, BinaryOperator<T> accumulator);
а также
Optional<T> reduce(BinaryOperator<T> accumulator);
Не может быть исправлено, потому что они использовали "BinaryOperator", но если "BiFunction" используется, то у нас больше гибкости
<U> U reduce(U identity, BiFunction<? super U, ? super T, ? extends U> accumulator, BinaryOperator<U> combiner)
Вместо:
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
Тот же комментарий относительно "Бинарный оператор"
(акцент мной).
Единственное оправдание, которое я нашел для того, чтобы не заменить BinaryOperator
с BiFunction
в конце концов был дан в ответе на это утверждение, в той же теме:
BinaryOperator не будет заменен на BiFunction, даже если, как вы сказали, он обеспечивает большую гибкость, BinaryOperator запрашивает, чтобы два параметра и тип возвращаемых данных были одинаковыми, поэтому он концептуально имеет больший вес (EG уже проголосовал за это).
Может быть, кто-то найдет точную ссылку на голосование Группы экспертов, которая управляла этим решением, но, возможно, эта цитата уже в достаточной мере отвечает на вопрос, почему это так…
На мой взгляд, просто нет никакого реального варианта использования предложенного улучшения. Предложенный Javadoc имеет еще 3 параметра типа и еще 5 групповых символов. Я полагаю, этого достаточно, чтобы упростить все это до официального API, потому что обычные Java-разработчики не хотят (часто даже не могут) сойти с ума, пытаясь сделать компилятор счастливым. Просто для записи, ваш reduce()
имеет 165 символов только в сигнатуре типа.
Кроме того, аргументы .reduce()
часто предоставляются в форме лямбда-выражений, поэтому нет смысла иметь более универсальные версии, когда такие выражения часто не содержат или имеют очень простую бизнес-логику и поэтому используются только один раз.
Например, я пользователь вашей фантастической библиотеки jOOQ, а также любопытный Java-разработчик, который любит головоломки общего характера, но часто мне не хватает простоты кортежей SQL, когда мне приходится вставлять символы подстановки в мои собственные интерфейсы из-за параметра type в Result<T>
и какие проблемы он создает при работе с интерфейсами типов записей - не то, что это ошибка JOOQ