Un buon sistema di tipo generico

28

È comunemente accettato che i generici Java abbiano fallito in alcuni modi importanti. La combinazione di caratteri jolly e limiti ha portato a un codice seriamente illeggibile.

Tuttavia, quando guardo altre lingue, non riesco davvero a trovare un sistema di tipo generico di cui i programmatori siano soddisfatti.

Se prendiamo i seguenti obiettivi di progettazione di un sistema di questo genere:

  • Produce sempre dichiarazioni di tipo di facile lettura
  • Facile da imparare (non c'è bisogno di rispolverare la covarianza, la contravarianza, ecc.)
  • massimizza il numero di errori in fase di compilazione

C'è qualche linguaggio che ha ragione? Se io google, l'unica cosa che vedo sono i reclami su come il sistema di tipi fa schifo nel linguaggio X. Questo tipo di complessità è inerente alla tipizzazione generica? Dovremmo semplicemente rinunciare a provare a verificare la sicurezza del tipo al 100% al momento della compilazione?

La mia domanda principale è quale sia il linguaggio che ha "capito bene" il meglio rispetto a questi tre obiettivi. Mi rendo conto che questo è soggettivo, ma finora non riesco nemmeno a trovare una lingua in cui non tutti i programmatori sono d'accordo sul fatto che il sistema di tipo generico sia un disastro.

Addendum: come notato, la combinazione di sottotipizzazione / ereditarietà e generici è ciò che crea la complessità, quindi sono davvero alla ricerca di un linguaggio che combini entrambi ed eviti l'esplosione della complessità.

    
posta Peter 17.10.2014 - 18:10
fonte

3 risposte

23

Mentre Generics è stato per decenni mainstream nella comunità della programmazione funzionale, l'aggiunta di generici a linguaggi di programmazione orientati agli oggetti offre alcune sfide uniche, in particolare l'interazione tra sottotitoli e generici.

Tuttavia, anche se ci concentrassimo su linguaggi di programmazione orientati agli oggetti, e in particolare su Java, sarebbe stato possibile progettare un sistema generico di gran lunga migliore:

  1. I tipi generici dovrebbero essere ammissibili ovunque siano altri tipi. In particolare, se T è un parametro di tipo, le seguenti espressioni dovrebbero essere compilate senza avvisi:

    object instanceof T; 
    T t = (T) object;
    T[] array = new T[1];
    

    Sì, questo richiede che i generici siano reificati, proprio come ogni altro tipo nella lingua.

  2. La covarianza e la controvarianza di un tipo generico dovrebbero essere specificate nella (o dedotta dalla) dichiarazione, piuttosto che ogni volta che viene usato il tipo generico, quindi possiamo scrivere

    Future<Provider<Integer>> s;
    Future<Provider<Number>> o = s; 
    

    anziché

    Future<? extends Provider<Integer>> s;
    Future<? extends Provider<? extends Number>> o = s;
    
  3. Poiché i tipi generici possono essere piuttosto lunghi, non è necessario specificarli in modo ridondante. Cioè dovremmo essere in grado di scrivere

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (var e : map.values()) {
        for (var list : e.values()) {
            for (var person : list) {
                greet(person);
            }
        }
    }
    

    anziché

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (Map<String, List<LanguageDesigner>> e : map.values()) {
        for (List<LanguageDesigner> list : e.values()) {
            for (LanguageDesigner person : list) {
                greet(person);
            }
        }
    }
    
  4. Qualsiasi tipo dovrebbe essere ammissibile come parametro di tipo, non solo come tipi di riferimento. (Se possiamo avere un int[] , perché non possiamo avere un List<int> )?

Tutto ciò è possibile in C #.

    
risposta data 18.10.2014 - 16:17
fonte
34

L'uso di sottotipi crea molte complicazioni durante la programmazione generica. Se insisti a usare un linguaggio con sottotipi, devi accettare che c'è una certa complessità intrinseca nella programmazione generica che ne deriva. Alcune lingue lo fanno meglio di altri, ma puoi solo portarlo fino a questo punto.

Confrontalo con i generici di Haskell, per esempio. Sono abbastanza semplici che se si utilizza l'inferenza del tipo, è possibile scrivere una funzione generica corretta per incidente . Infatti, se si specifica un singolo tipo, il compilatore spesso dice a se stesso: "Beh, io avrei intenzione di renderlo generico, ma tu mi hai chiesto di farlo solo per ints, quindi qualunque cosa".

Certo, le persone usano il sistema di tipi di Haskell in modi sorprendentemente complessi, rendendolo la rovina di ogni principiante, ma il sistema di tipo sottostante è elegante e molto ammirato.

    
risposta data 17.10.2014 - 18:48
fonte
14

C'è stato un bel po 'di ricerche sulla combinazione di generici con sottotipi in corso circa 20 anni fa. Il linguaggio di programmazione Thor sviluppato dal gruppo di ricerca di Barbara Liskov al MIT aveva una nozione di clausole "where" che ti permettono di specificare i requisiti del tipo su cui stai parametrizzando. (Questo è simile a ciò che C ++ sta cercando di fare con Concetti .)

Il documento che descrive i generici di Thor e come interagiscono con i sottotipi di Thor è: Giorno, m; Gruber, R; Liskov, B; Myers, AC: Sottotipi vs. clausole: polimorfismo parametrico vincolante , ACM Conf. Su Prog, Sys, Lang e app orientati agli oggetti , (OOPSLA-10): 156-158, 1995.

Credo che, a loro volta, si siano basati sul lavoro svolto su Emerald alla fine degli anni '80. (Non ho letto quel lavoro, ma il riferimento è: Black, A; Hutchinson, N; Jul, E; Levy, H; Carter, L: Distribuzione e tipi astratti in Emerald , _IEEE T. Software Eng., 13 (1): 65-76, 1987.

Sia Thor che Emerald erano "lingue accademiche", quindi probabilmente non hanno avuto abbastanza utilizzo da far capire alla gente se le clausole (concetti) in realtà risolvono i problemi reali. È interessante leggere l'articolo di Bjarne Stroustrup sul perché il primo tentativo di Concepts in C ++ è fallito: Stroustrup, B: Decisione" Rimuovi concetti "C ++ 0x , Dr Dobbs , 22 luglio 2009. (Ulteriori informazioni su Home page di Stroustrup .)

Un'altra direzione che le persone sembrano provare è una cosa chiamata tratti . Ad esempio, il linguaggio di programmazione di Rust di Mozilla utilizza i tratti. A quanto ho capito (che potrebbe essere completamente sbagliato), dichiarare che una classe soddisfa un tratto è molto simile a dire che una classe implementa un'interfaccia, ma stai dicendo che "si comporta come un" piuttosto che "è un". Sembra che i nuovi linguaggi di programmazione di Swift utilizzino un concetto simile di protocolli a specifica i vincoli sui parametri ai generici .

    
risposta data 18.10.2014 - 00:29
fonte

Leggi altre domande sui tag