在Stream.reduce()这样的API中选择不变性有什么好理由?

Luk*_*der 20 java covariance contravariance java-stream invariance

回顾Java 8 StreamAPI设计,我对Stream.reduce()参数的泛型不变性感到惊讶:

<U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner)
Run Code Online (Sandbox Code Playgroud)

相同API的看似更通用的版本可能在个别引用上应用了协方差/逆变U,例如:

<U> U reduce(U identity,
             BiFunction<? super U, ? super T, ? extends U> accumulator,
             BiFunction<? super U, ? super U, ? extends U> combiner)
Run Code Online (Sandbox Code Playgroud)

这将允许以下目前无法实现的目标:

// 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);
Run Code Online (Sandbox Code Playgroud)

解决方法,使用方法引用将类型"强制"为目标类型:

double sum = stream.reduce(0.0, numberAdder::apply, numberAdder::apply);
Run Code Online (Sandbox Code Playgroud)

C#没有Func(T1, T2, TResult)使用声明 - 站点差异定义如下的特定问题,这意味着任何使用的API Func都可以免费获得此行为:

public delegate TResult Func<in T1, in T2, out TResult>(
    T1 arg1,
    T2 arg2
)
Run Code Online (Sandbox Code Playgroud)

与现有设计相比,现有设计的优点(可能还有EG决策的原因)有哪些?

或者,换句话说,我可能会忽略的建议设计的注意事项(例如类型推断困难,并行化约束或特定于约简操作的约束,例如关联性,对未来Java声明站点方差的预期BiFunction<in T, in U, out R>, ...)?

Mar*_*o13 5

爬过lambda开发的历史并隔离这个决定的"原因"的原因很难 - 所以最终,人们将不得不等待其中一个开发人员回答这个问题.

一些提示可能如下:

  • 流接口经历了多次迭代和重构.在Stream界面的最早版本之一中,有一些专用reduce方法,那些最接近reduce问题中方法的方法仍然被称为Stream#fold.这其中已经收到BinaryOperatorcombiner参数.

  • 有趣的是,很长一段时间,lambda提案包括一个专用接口Combiner<T,U,R>.与直觉相反,这不是combinerStream#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'的相同评论

    (我强调).


我发现没有替换BinaryOperatora 的唯一理由BiFunction最终是在对此语句响应中给出的,在同一个线程中:

BinaryOperator不会被BiFunction取代,即使如你所说,它引入了更多的灵活性,BinaryOperator要求两个参数和返回类型相同,因此它在概念上更加重要(EG已经投票了).

也许有人可以挖掘出专家组对这一决定的投票的特定参考,但也许这句话已经充分回答了为什么它是这样的问题......