Qual è il motivo dell'utilizzo di un'interfaccia rispetto a un tipo genericamente vincolato


15

Nei linguaggi orientati agli oggetti che supportano parametri di tipo generico (noti anche come modelli di classe e polimorfismo parametrico, sebbene ovviamente ogni nome abbia connotazioni diverse), è spesso possibile specificare un vincolo di tipo sul parametro di tipo, in modo tale che sia discendente da un altro tipo. Ad esempio, questa è la sintassi in C #:

//for classes:
class ExampleClass<T> where T : I1 {

}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
        ...
}

Quali sono i motivi per utilizzare i tipi di interfaccia effettivi rispetto ai tipi vincolati da tali interfacce? Ad esempio, quali sono i motivi per creare la firma del metodo I2 ExampleMethod(I2 value)?


4
i template di classe (C ++) sono qualcosa di completamente diverso e molto più potente dei generici misericordiosi. Anche se le lingue con generici hanno preso in prestito la sintassi del modello per loro.
Deduplicatore

I metodi di interfaccia sono chiamate indirette, mentre i metodi di tipo possono essere chiamate dirette. Quindi quest'ultimo può essere più veloce del primo e, nel caso dei refparametri del tipo di valore, potrebbe effettivamente modificare il tipo di valore.
user541686

@Deduplicatore: considerando che i generici sono più vecchi dei modelli, non riesco a vedere come i generici possano aver preso in prestito qualsiasi cosa dai modelli, dalla sintassi o altro.
Jörg W Mittag,

3
@ JörgWMittag: ho il sospetto che da "linguaggi orientati agli oggetti che supportano i generici", Deduplicator avrebbe potuto capire "Java e C #" piuttosto che "ML e Ada". Quindi l'influenza del C ++ sul primo è chiara, nonostante non tutte le lingue che hanno un polimorfismo generico o parametrico mutuato dal C ++.
Steve Jessop,

2
@SteveJessop: ML, Ada, Eiffel, Haskell precedono i modelli C ++, Scala, F #, OCaml sono arrivati ​​dopo, e nessuno di loro condivide la sintassi di C ++. (È interessante notare che anche D, che prende in prestito pesantemente dal C ++, in particolare i modelli, non condivide la sintassi del C ++. "Java e C #" è una visione piuttosto ristretta di "linguaggi generici", penso.
Jörg W Mittag,

Risposte:


21

Utilizzando la versione parametrica dà

  1. Ulteriori informazioni per gli utenti della funzione
  2. Limita il numero di programmi che puoi scrivere (verifica dei bug gratuita)

Come esempio casuale, supponiamo di avere un metodo che calcola le radici di un'equazione quadratica

int solve(int a, int b, int c) {
  // My 7th grade math teacher is laughing somewhere
}

E poi vuoi che funzioni su altri tipi di numeri come le cose oltre int. Puoi scrivere qualcosa del genere

Num solve(Num a, Num b, Num c){
  ...
}

Il problema è che questo non dice quello che vuoi. Dice

Dammi 3 cose che sono numeri (non necessariamente allo stesso modo) e ti restituirò una sorta di numero

Non possiamo fare qualcosa come int sol = solve(a, b, c)if a, be csiamo ints perché non sappiamo che il metodo restituirà un intalla fine! Questo porta ad alcune danze imbarazzanti con abbattimenti e preghiere se vogliamo usare la soluzione in un'espressione più ampia.

All'interno della funzione, qualcuno potrebbe consegnarci un float, un bigint e gradi e dovremmo aggiungerli e moltiplicarli insieme. Vorremmo rifiutarlo staticamente perché le operazioni tra queste 3 classi saranno incomprensibili. I gradi sono mod 360 quindi non sarà così a.plus(b) = b.plus(a)e sorgeranno simili simili.

Se usiamo il polimorfismo parametrico con il sottotipo possiamo escludere tutto questo perché il nostro tipo dice effettivamente cosa intendiamo

<T : Num> T solve(T a, T b, T c)

O a parole "Se mi dai un tipo che è un numero, posso risolvere equazioni con quei coefficienti".

Questo succede anche in molti altri posti. Un'altra fonte buona di esempi sono funzioni che astratto su una sorta di contenitore, ala reverse, sort, map, etc.


8
In breve, la versione generica garantisce che tutti e tre gli input (e l'output) saranno dello stesso tipo di numero.
MathematicalOrchid

Tuttavia, questo non è all'altezza quando non controlli il tipo in questione (e quindi non puoi aggiungere un'interfaccia ad esso). Per la massima generalità dovresti accettare un'interfaccia parametrizzata dal tipo di argomento (ad es. Num<int>) Come argomento aggiuntivo. È sempre possibile implementare l'interfaccia per qualsiasi tipo tramite delega. Questo è essenzialmente ciò che le classi di tipi di Haskell sono, tranne molto più noiose da usare poiché devi passare esplicitamente nell'interfaccia.
Doval,

16

Quali sono i motivi per utilizzare i tipi di interfaccia effettivi rispetto ai tipi vincolati da tali interfacce?

Perché è quello che ti serve ...

IFoo Fn(IFoo x);
T Fn<T>(T x) where T: IFoo;

sono due firme decisamente diverse. Il primo prende qualsiasi tipo implementando l'interfaccia e l'unica garanzia che fa è che il valore di ritorno soddisfi l'interfaccia.

Il secondo prende qualsiasi tipo implementando l'interfaccia e garantisce che ritornerà almeno quel tipo (piuttosto che qualcosa che soddisfa l'interfaccia meno restrittiva).

A volte, vuoi la garanzia più debole. A volte vuoi quello più forte.


Puoi fare un esempio di come utilizzeresti la versione di garanzia più debole?
GregRos,

4
@GregRos - Ad esempio, in alcuni codici parser che ho scritto. Ho una funzione Orche accetta due Parseroggetti (una classe base astratta, ma il principio vale) e restituisce un nuovo Parser(ma con un tipo diverso). L'utente finale non dovrebbe sapere o preoccuparsi di quale sia il tipo concreto.
Telastyn,

In C # immagino che restituire una T diversa da quella che è stata trasmessa sia quasi impossibile (senza dolore di riflessione) senza il nuovo vincolo, rendendo la tua forte garanzia abbastanza inutile da sola.
Ntsc Cobalt

1
@NtscCobalt: è più utile quando si combinano sia la programmazione parametrica che quella generica dell'interfaccia. Ad esempio quello che LINQ fa sempre (accetta un IEnumerable<T>, ne restituisce un altro IEnumerable<T>che ad esempio è effettivamente un OrderedEnumerable<T>)
Ben Voigt

2

L'uso di generici vincolati per i parametri del metodo può consentire a un metodo di ottenere il suo tipo di ritorno in base a quello della cosa passata. In .NET possono anche avere ulteriori vantaggi. Tra loro:

  1. Un metodo che accetta un generico vincolato come parametro refo outpuò passare una variabile che soddisfa il vincolo; al contrario, un metodo non generico con un parametro di tipo interfaccia sarebbe limitato all'accettazione di variabili di quel tipo esatto di interfaccia.

  2. Un metodo con parametro di tipo generico T può accettare raccolte generiche di T. Un metodo che accetta un IList<T> where T:IAnimalsarà in grado di accettare un List<SiameseCat>, ma un metodo che voleva un IList<Animal>non sarebbe in grado di farlo.

  3. A volte un vincolo può specificare un'interfaccia in termini di tipo generico, ad es where T:IComparable<T>.

  4. Una struttura che implementa un'interfaccia può essere mantenuta come tipo di valore quando passata a un metodo che accetta un parametro generico vincolato, ma deve essere inscatolata quando passata come tipo di interfaccia. Questo può avere un enorme effetto sulla velocità.

  5. Un parametro generico può avere più vincoli, mentre non esiste altro modo per specificare un parametro di "un tipo che implementa sia IFoo che IBar". A volte questa può essere un'arma a doppio taglio, poiché il codice che ha ricevuto un parametro di tipo IFootroverà molto difficile passarlo a un tale metodo aspettandosi un generico a doppio vincolo, anche se l'istanza in questione soddisferebbe tutti i vincoli.

Se in una situazione particolare non ci fosse alcun vantaggio nell'usare un generico, allora semplicemente accettare un parametro del tipo di interfaccia. L'uso di un generico costringerà il sistema di tipi e JITter a fare un lavoro extra, quindi se non ci sono benefici non si dovrebbe farlo. D'altra parte, è molto comune che si applichi almeno uno dei vantaggi di cui sopra.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.