2016-02-28 11 views
20

Revisione Java 8 Stream progettazione API, sono rimasto sorpreso dalla invarianza generica sulle Stream.reduce() argomenti:Quali sono i buoni motivi per scegliere l'invarianza in un'API come Stream.reduce()?

<U> U reduce(U identity, 
      BiFunction<U,? super T,U> accumulator, 
      BinaryOperator<U> combiner) 

Una versione apparentemente più versatile della stessa API potrebbe essere applicato covarianza/controvarianza sui singoli riferimenti a U, come ad come:

<U> U reduce(U identity, 
      BiFunction<? super U, ? super T, ? extends U> accumulator, 
      BiFunction<? super U, ? super U, ? extends U> combiner) 

Ciò consentirebbe il seguente, che non è possibile, attualmente:

// 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); 

Soluzione, utilizzare metodo riferimenti a "costringere" i tipi nel tipo di destinazione:

double sum = stream.reduce(0.0, numberAdder::apply, numberAdder::apply); 

C# non hanno questo problema particolare, come Func(T1, T2, TResult) è definito come segue, utilizzando varianza dichiarazione loco, il che significa che ogni API utilizzando Func ottiene questo comportamento gratuitamente:

public delegate TResult Func<in T1, in T2, out TResult>(
    T1 arg1, 
    T2 arg2 
) 

Quali sono i vantaggi (e possibilmente, le ragioni per le decisioni EG) del progetto esistente sopra il disegno suggerito?

O, ha chiesto in modo diverso, quali sono gli avvertimenti del design suggerito che io possa essere vista (ad esempio di tipo difficoltà di inferenza, vincoli parallelizzazione, o vincoli specifici per l'operazione di riduzione, come ad esempio l'associatività, anticipazione di un futuro declaration- di Java varianza del sito su BiFunction<in T, in U, out R>, ...)?

+2

I sospetto che ci sarebbe un tipo di inferenza più difficile in quanto sospetto che questo lascerebbe alcuni tipi irrisolvibili senza essere espliciti (sconfiggendo il punto di inferenza) –

+1

@PeterLawrey: Anche questa era la mia preoccupazione, ma 1) puoi provarlo? :) 2) Scala conosce 'TraversableOnce.reduce [A1>: A] (op: (A1, A1) ⇒ A1): A1' –

+0

Un modo sarebbe scrivere un'API come questa e vedere cosa succede quando provi ad usare esso. –

risposta

5

strisciare attraverso la storia dello sviluppo lambda e isolando "iL" motivo di questa decisione è difficile - Quindi alla fine, si dovrà aspettare che uno degli sviluppatori risponda a questa domanda.

Alcuni indizi possono essere i seguenti:

  • Le interfacce streaming hanno subito diverse iterazioni e refactoring.In una delle prime versioni dell'interfaccia Stream, sono stati dedicati i metodi reduce e quello più vicino al metodo reduce nella domanda era ancora chiamato Stream#fold. Questo ha già ricevuto un BinaryOperator come parametro combiner.

  • È interessante notare che per un po 'di tempo la proposta lambda includeva un'interfaccia dedicata Combiner<T,U,R>. Contrariamente a ciò, questo non è stato utilizzato come combiner nella funzione Stream#reduce. Invece, è stato utilizzato come reducer, che sembra essere quello che oggigiorno viene definito come lo accumulator. Tuttavia, l'interfaccia Combiner era replaced with BiFunction in a later revision.

  • La somiglianza più sorprendente con la domanda qui si trova in un thread about the Stream#flatMap signature at the mailing list, che viene quindi trasformato nella domanda generale sulle varianze delle firme del metodo dello stream. Lo hanno risolto questi in alcuni luoghi, ad esempio

    Come Brian mi corregga:

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    invece di:

    <R> Stream<R> flatMap(Function<T, Stream<? extends R>> mapper);

    ma ho notato che in alcuni luoghi, questo non era possibile:

    T reduce(T identity, BinaryOperator<T> accumulator);

    e

    Optional<T> reduce(BinaryOperator<T> accumulator);

    non può essere risolto perché hanno usato 'BinaryOperator', ma se 'BiFunction' è utilizzato allora abbiamo una maggiore flessibilità

    <U> U reduce(U identity, BiFunction<? super U, ? super T, ? extends U> accumulator, BinaryOperator<U> combiner)

    Invece di:

    <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

    Stessa osservazione per quanto riguarda 'BinaryOperator'

    (enfasi da me).


L'unica giustificazione che ho trovato per non sostituendo il BinaryOperator con un BiFunction finalmente è stato dato nella response to this statement, in the same thread:

BinaryOperator non sarà sostituita da BiFunction anche se, come hai detto, introduce più flessibilità, un Operatore Binario chiede che i due parametri e il tipo restituito siano gli stessi in modo che abbia concettualmente più peso (il PE già vota su questo).

Forse qualcuno può scavare un riferimento perticular del voto del gruppo di esperti che ha governato questa decisione, ma forse questa citazione già sufficientemente risponde alla domanda del perché è il modo in cui è ...

+0

Grazie per la tua archeologia. Questo spiega lo status quo, anche se sono un po 'deluso :) –

1

A mio parere è solo che non esiste un vero caso di utilizzo per il miglioramento proposto. Il Javadoc proposto ha altri 3 parametri di tipo e altri 5 caratteri jolly. Suppongo sia sufficiente semplificare l'intera cosa all'API ufficiale perché i normali sviluppatori Java non vogliono (spesso non sono neppure in grado) di perdere la testa cercando di rendere il compilatore felice. Solo per la cronaca, il tuo reduce() ha 165 caratteri solo nella firma del tipo.

Inoltre, argomenti per .reduce() sono spesso forniti in forma di espressioni lambda, quindi non c'è vero e proprio punto di avere versioni più versatili quando tali espressioni spesso contengono nessuna o la logica di business molto semplice e sono quindi utilizzati solo una volta.

Ad esempio, sono un utente della tua fantastica libreria jOOQ e anche un curioso sviluppatore Java che ama i puzzle generici, ma spesso mi manca la semplicità delle tuple SQL quando devo inserire i caratteri jolly nelle mie interfacce a causa del tipo di parametro in Result<T> e il tipo di problemi che genera quando si tratta di interfacce dei tipi di record - non che si tratta di un guasto jOOQ

+0

*" perché i normali sviluppatori Java non vogliono (spesso non sono nemmeno in grado) di perdere la testa cercando di rendere felice il compilatore "* - il suggerimento sarebbe più difficile da leggere, sì, ma renderebbe (un po ') più facile agli utenti" rendere il compilatore felice ". Ecco dove la varianza può essere così utile –

+1

Se hai trovato un caso d'uso reale, forse vale la pena di postarlo nella domanda originale! Ho provato a capirne uno ma non sono riuscito a – Raffaele

+1

Non mi sembra che questo risponda alla domanda - se il tuo reclamo è che non è stato aggiunto perché non c'era interesse per favore esegui il backup con prove dalle discussioni dell'API . –

Problemi correlati